diff options
864 files changed, 41090 insertions, 12229 deletions
diff --git a/Android.bp b/Android.bp index e63463c07313..d4a04cc43fe7 100644 --- a/Android.bp +++ b/Android.bp @@ -49,6 +49,9 @@ java_defaults { "rs/java/**/*.java", ":framework-javastream-protos", + // TODO: Resolve circular library dependency and remove media1-srcs and mediasession2-srcs + ":media1-srcs", + ":mediasession2-srcs", "core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl", "core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl", @@ -471,14 +474,11 @@ java_defaults { "media/java/android/media/IAudioRoutesObserver.aidl", "media/java/android/media/IAudioService.aidl", "media/java/android/media/IAudioServerStateDispatcher.aidl", - "media/java/android/media/IMediaController2.aidl", "media/java/android/media/IMediaHTTPConnection.aidl", "media/java/android/media/IMediaHTTPService.aidl", "media/java/android/media/IMediaResourceMonitor.aidl", "media/java/android/media/IMediaRouterClient.aidl", "media/java/android/media/IMediaRouterService.aidl", - "media/java/android/media/IMediaSession2.aidl", - "media/java/android/media/IMediaSession2Service.aidl", "media/java/android/media/IMediaScannerListener.aidl", "media/java/android/media/IMediaScannerService.aidl", "media/java/android/media/IPlaybackConfigDispatcher.aidl", @@ -504,11 +504,7 @@ java_defaults { "media/java/android/media/session/ICallback.aidl", "media/java/android/media/session/IOnMediaKeyListener.aidl", "media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl", - "media/java/android/media/session/ISession.aidl", "media/java/android/media/session/ISession2TokensListener.aidl", - "media/java/android/media/session/ISessionCallback.aidl", - "media/java/android/media/session/ISessionController.aidl", - "media/java/android/media/session/ISessionControllerCallback.aidl", "media/java/android/media/session/ISessionManager.aidl", "media/java/android/media/soundtrigger/ISoundTriggerDetectionService.aidl", "media/java/android/media/soundtrigger/ISoundTriggerDetectionServiceClient.aidl", @@ -523,8 +519,6 @@ java_defaults { "media/java/android/media/tv/ITvInputSessionCallback.aidl", "media/java/android/media/tv/ITvRemoteProvider.aidl", "media/java/android/media/tv/ITvRemoteServiceInput.aidl", - "media/java/android/service/media/IMediaBrowserService.aidl", - "media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl", "telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl", "telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl", "telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl", @@ -637,6 +631,7 @@ java_defaults { "wifi/java/android/net/wifi/ISoftApCallback.aidl", "wifi/java/android/net/wifi/ITrafficStateCallback.aidl", "wifi/java/android/net/wifi/IWifiManager.aidl", + "wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl", @@ -691,6 +686,7 @@ java_defaults { "location/java", "lowpan/java", "media/java", + "media/apex/java", "media/mca/effect/java", "media/mca/filterfw/java", "media/mca/filterpacks/java", @@ -723,8 +719,6 @@ java_defaults { exclude_srcs: [ // See comment on framework-atb-backward-compatibility module below "core/java/android/content/pm/AndroidTestBaseUpdater.java", - // See comment on framework-oahl-backward-compatibility module below - "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", ], no_framework_libs: true, @@ -732,15 +726,27 @@ java_defaults { "ext", ], + jarjar_rules: "jarjar_rules_hidl.txt", + static_libs: [ "apex_aidl_interface-java", "networkstack-aidl-interfaces-java", "framework-protos", + "game-driver-protos", "mediaplayer2-protos", "android.hidl.base-V1.0-java", "android.hardware.cas-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.0-java", + "android.hardware.radio-V1.1-java", + "android.hardware.radio-V1.2-java", + "android.hardware.radio-V1.3-java", + "android.hardware.radio-V1.4-java", + "android.hardware.radio.config-V1.0-java", + "android.hardware.radio.config-V1.1-java", + "android.hardware.radio.config-V1.2-java", + "android.hardware.radio.deprecated-V1.0-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.thermal-V1.0-java", "android.hardware.thermal-V1.1-java", @@ -748,15 +754,13 @@ java_defaults { "android.hardware.tv.input-V1.0-java-constants", "android.hardware.usb-V1.0-java-constants", "android.hardware.usb-V1.1-java-constants", + "android.hardware.usb-V1.2-java-constants", + "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", "android.hardware.wifi-V1.0-java-constants", - "android.hardware.radio-V1.0-java", - "android.hardware.radio-V1.3-java", - "android.hardware.radio-V1.4-java", - "android.hardware.usb.gadget-V1.0-java", "networkstack-aidl-interfaces-java", "netd_aidl_interface-java", "devicepolicyprotosnano", @@ -800,11 +804,7 @@ java_library { name: "framework-annotation-proc", defaults: ["framework-defaults"], // Use UsedByApps annotation processor - annotation_processors: ["unsupportedappusage-annotation-processor"], - // b/25860419: annotation processors must be explicitly specified for grok - annotation_processor_classes: [ - "android.processor.unsupportedappusage.UsedByAppsProcessor", - ], + plugins: ["unsupportedappusage-annotation-processor"], } // A host library including just UnsupportedAppUsage.java so that the annotation @@ -1263,7 +1263,7 @@ stubs_defaults { ":non_openjdk_javadoc_files", ":android_icu4j_src_files_for_docs", ":conscrypt_public_api_files", - ":media2-srcs", + ":media-srcs-without-aidls", "test-mock/src/**/*.java", "test-runner/src/**/*.java", ], @@ -1325,7 +1325,7 @@ stubs_defaults { ":non_openjdk_javadoc_files", ":android_icu4j_src_files_for_docs", ":conscrypt_public_api_files", - ":media2-srcs", + ":media-srcs-without-aidls", ], srcs_lib: "framework", srcs_lib_whitelist_dirs: frameworks_base_subdirs, @@ -1771,6 +1771,7 @@ filegroup { name: "framework-media-annotation-srcs", srcs: [ "core/java/android/annotation/CallbackExecutor.java", + "core/java/android/annotation/CallSuper.java", "core/java/android/annotation/DrawableRes.java", "core/java/android/annotation/IntDef.java", "core/java/android/annotation/LongDef.java", @@ -1779,6 +1780,7 @@ filegroup { "core/java/android/annotation/RequiresPermission.java", "core/java/android/annotation/SdkConstant.java", "core/java/android/annotation/StringDef.java", + "core/java/android/annotation/SystemApi.java", "core/java/android/annotation/TestApi.java", "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/com/android/internal/annotations/GuardedBy.java", diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java index a7a81f2d20bb..767434d0831c 100644 --- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java +++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java @@ -91,7 +91,7 @@ public class TextClassifierPerfTest { private static ConversationActions.Request createConversationActionsRequest(CharSequence text) { ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(text) .build(); return new ConversationActions.Request.Builder(Collections.singletonList(message)) diff --git a/api/current.txt b/api/current.txt index 1520a01a3965..124ad584bc41 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3941,7 +3941,8 @@ package android.app { method public boolean isBackgroundRestricted(); method @Deprecated public boolean isInLockTaskMode(); method public boolean isLowRamDevice(); - method public static boolean isRunningInTestHarness(); + method @Deprecated public static boolean isRunningInTestHarness(); + method public static boolean isRunningInUserTestHarness(); method public static boolean isUserAMonkey(); method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); @@ -5100,7 +5101,7 @@ package android.app { } public class KeyguardManager { - method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); + method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method @Deprecated public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -5465,9 +5466,11 @@ package android.app { public static final class Notification.BubbleMetadata implements android.os.Parcelable { method public int describeContents(); + method public boolean getAutoExpandBubble(); method public int getDesiredHeight(); method public android.graphics.drawable.Icon getIcon(); method public android.app.PendingIntent getIntent(); + method public boolean getSuppressInitialNotification(); method public CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR; @@ -5476,9 +5479,11 @@ package android.app { public static class Notification.BubbleMetadata.Builder { ctor public Notification.BubbleMetadata.Builder(); method public android.app.Notification.BubbleMetadata build(); + method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean); method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int); method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon); method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent); + method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean); method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence); } @@ -5792,6 +5797,7 @@ package android.app { method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method @Nullable public String getNotificationDelegate(); method public android.app.NotificationManager.Policy getNotificationPolicy(); + method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName); method public boolean isNotificationListenerAccessGranted(android.content.ComponentName); method public boolean isNotificationPolicyAccessGranted(); method public void notify(int, android.app.Notification); @@ -6577,7 +6583,6 @@ package android.app.admin { } public class DevicePolicyManager { - method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String); method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int); method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting); @@ -6607,7 +6612,7 @@ package android.app.admin { method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName); method public boolean getCameraDisabled(@Nullable android.content.ComponentName); method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException; - method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName); + method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName); method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName); method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName); @@ -6697,7 +6702,6 @@ package android.app.admin { method public int logoutUser(@NonNull android.content.ComponentName); method public void reboot(@NonNull android.content.ComponentName); method public void removeActiveAdmin(@NonNull android.content.ComponentName); - method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String); method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String); method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int); @@ -6719,6 +6723,7 @@ package android.app.admin { method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean); method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean); method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException; + method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>); method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean); method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); @@ -6786,7 +6791,7 @@ package android.app.admin { method public void uninstallCaCert(@Nullable android.content.ComponentName, byte[]); method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting); method public void wipeData(int); - method public void wipeData(int, CharSequence); + method public void wipeData(int, @NonNull CharSequence); field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"; field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED"; @@ -6822,6 +6827,7 @@ package android.app.admin { field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION"; field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES"; field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN"; + field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY"; field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE"; field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME"; @@ -6929,6 +6935,7 @@ package android.app.admin { field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 + field public static final int WIPE_SILENTLY = 8; // 0x8 } public abstract static class DevicePolicyManager.InstallUpdateCallback { @@ -8608,6 +8615,13 @@ package android.bluetooth { method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int); } + public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile { + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { method public boolean connect(android.bluetooth.BluetoothDevice); method public boolean disconnect(android.bluetooth.BluetoothDevice); @@ -8703,6 +8717,7 @@ package android.bluetooth { field public static final int GATT_SERVER = 8; // 0x8 field public static final int HEADSET = 1; // 0x1 field @Deprecated public static final int HEALTH = 3; // 0x3 + field public static final int HEARING_AID = 21; // 0x15 field public static final int HID_DEVICE = 19; // 0x13 field public static final int SAP = 10; // 0xa field public static final int STATE_CONNECTED = 2; // 0x2 @@ -10211,6 +10226,7 @@ package android.content { field public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED"; field public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED"; field public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE"; + field public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME"; field public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED"; field public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED"; field public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE"; @@ -10346,6 +10362,7 @@ package android.content { field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3 field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0 field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; + field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS"; field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL"; field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE"; @@ -11207,6 +11224,7 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle); + method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); method public java.util.List<android.os.UserHandle> getProfiles(); @@ -11234,6 +11252,14 @@ package android.content.pm { field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST"; } + public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable { + method public int describeContents(); + method public long getTotalUsageLimit(); + method public long getUsageRemaining(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR; + } + public abstract static class LauncherApps.Callback { ctor public LauncherApps.Callback(); method public abstract void onPackageAdded(String, android.os.UserHandle); @@ -11409,6 +11435,7 @@ package android.content.pm { method public int getSessionId(); method public long getSize(); method public int getStagedSessionErrorCode(); + method public String getStagedSessionErrorMessage(); method public boolean isActive(); method public boolean isMultiPackage(); method public boolean isSealed(); @@ -11430,6 +11457,7 @@ package android.content.pm { method public void setAppIcon(@Nullable android.graphics.Bitmap); method public void setAppLabel(@Nullable CharSequence); method public void setAppPackageName(@Nullable String); + method public void setInstallAsApex(); method public void setInstallLocation(int); method public void setInstallReason(int); method public void setMultiPackage(); @@ -11497,7 +11525,7 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int); method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract int getApplicationEnabledSetting(String); + method public abstract int getApplicationEnabledSetting(@NonNull String); method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11505,7 +11533,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo); method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int); - method public abstract int getComponentEnabledSetting(android.content.ComponentName); + method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); @@ -11571,8 +11599,8 @@ package android.content.pm { method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int); method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public abstract void setApplicationCategoryHint(@NonNull String, int); - method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int); - method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); + method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); + method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); method public abstract void setInstallerPackageName(String, String); method public abstract void updateInstantAppCookie(@Nullable byte[]); method public abstract void verifyPendingInstall(int, int); @@ -11652,6 +11680,7 @@ package android.content.pm { field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait"; field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users"; + field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"; field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature"; field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer"; @@ -11671,6 +11700,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television"; field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; @@ -13917,8 +13947,6 @@ package android.graphics { @AnyThread public abstract class ColorSpace { method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]); method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation); - method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int); - method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace); @@ -14615,8 +14643,8 @@ package android.graphics { public class Picture { ctor public Picture(); ctor public Picture(android.graphics.Picture); - method public android.graphics.Canvas beginRecording(int, int); - method public void draw(android.graphics.Canvas); + method @NonNull public android.graphics.Canvas beginRecording(int, int); + method public void draw(@NonNull android.graphics.Canvas); method public void endRecording(); method public int getHeight(); method public int getWidth(); @@ -14868,7 +14896,7 @@ package android.graphics { } public final class RenderNode { - ctor public RenderNode(String); + ctor public RenderNode(@Nullable String); method public int computeApproximateMemoryUsage(); method public void discardDisplayList(); method public void endRecording(); @@ -16468,6 +16496,7 @@ package android.hardware.biometrics { ctor public BiometricPrompt.Builder(android.content.Context); method public android.hardware.biometrics.BiometricPrompt build(); method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); + method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); @@ -17074,6 +17103,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_TRANSLATION; field @Deprecated public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE; + field public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH; @@ -22253,7 +22283,7 @@ package android.inputmethodservice { method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo); method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo); method public void onUpdateSelection(int, int, int, int, int, int); - method public void onViewClicked(boolean); + method @Deprecated public void onViewClicked(boolean); method public void onWindowHidden(); method public void onWindowShown(); method public void requestHideSelf(int); @@ -24635,6 +24665,7 @@ package android.media { method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String); + method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int); method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; @@ -25404,6 +25435,7 @@ package android.media { method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method public Object attachAuxEffect(int); method public boolean cancelCommand(@NonNull Object); + method public void clearDrmEventCallback(); method public Object clearNextDataSources(); method public void clearPendingCommands(); method public void close(); @@ -25920,7 +25952,6 @@ package android.media { method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); method public final void removeSession(@NonNull android.media.MediaSession2); - field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; } public static class MediaSession2Service.MediaNotification { @@ -26591,12 +26622,9 @@ package android.media.audiofx { field public static final int SUCCESS = 0; // 0x0 } - public static final class AudioEffect.Descriptor implements android.os.Parcelable { + public static class AudioEffect.Descriptor { ctor public AudioEffect.Descriptor(); ctor public AudioEffect.Descriptor(String, String, String, String, String); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR; field public String connectMode; field public String implementor; field public String name; @@ -27446,6 +27474,7 @@ package android.media.session { method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens(); method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo); method public void notifySession2Created(@NonNull android.media.Session2Token); + method public void notifySession2Destroyed(@NonNull android.media.Session2Token); method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); } @@ -27460,7 +27489,6 @@ package android.media.session { public static final class MediaSessionManager.RemoteUserInfo { ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int); - ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder); method public String getPackageName(); method public int getPid(); method public int getUid(); @@ -28469,6 +28497,7 @@ package android.net { public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); @@ -28578,6 +28607,28 @@ package android.net { field public int serverAddress; } + public final class DnsResolver { + method public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException; + field public static final int CLASS_IN = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.InetAddressAnswerListener { + method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>); + } + + public static interface DnsResolver.RawAnswerListener { + method public void onAnswer(@Nullable byte[]); + } + public class InetAddresses { method public static boolean isNumericAddress(String); method public static java.net.InetAddress parseNumericAddress(String); @@ -28951,6 +29002,29 @@ package android.net { ctor public SSLSessionCache(android.content.Context); } + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); @@ -29161,6 +29235,7 @@ package android.net { method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); + method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); @@ -29769,7 +29844,7 @@ package android.net.wifi { method @Deprecated public boolean disableNetwork(int); method @Deprecated public boolean disconnect(); method @Deprecated public boolean enableNetwork(int, boolean); - method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); method public android.net.wifi.WifiInfo getConnectionInfo(); method public android.net.DhcpInfo getDhcpInfo(); method public int getMaxNumberOfNetworkSuggestionsPerApp(); @@ -30260,26 +30335,26 @@ package android.net.wifi.p2p { } public class WifiP2pManager { - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener); method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener); method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener); method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener); method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener); method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener); method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener); method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener); @@ -35227,11 +35302,16 @@ package android.os { public abstract class VibrationEffect implements android.os.Parcelable { method public static android.os.VibrationEffect createOneShot(long, int); + method public static android.os.VibrationEffect createPrebaked(int); method public static android.os.VibrationEffect createWaveform(long[], int); method public static android.os.VibrationEffect createWaveform(long[], int[], int); method public int describeContents(); field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff + field public static final int EFFECT_CLICK = 0; // 0x0 + field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1 + field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5 + field public static final int EFFECT_TICK = 2; // 0x2 } public abstract class Vibrator { @@ -38172,6 +38252,8 @@ package android.provider { field public static final String MEDIA_SCANNER_VOLUME = "volume"; field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service"; field public static final String UNKNOWN_STRING = "<unknown>"; + field public static final String VOLUME_EXTERNAL = "external"; + field public static final String VOLUME_INTERNAL = "internal"; } public static final class MediaStore.Audio { @@ -38378,28 +38460,28 @@ package android.provider { field public static final android.net.Uri INTERNAL_CONTENT_URI; } - public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns { - ctor public MediaStore.Images.Thumbnails(); + @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns { + ctor @Deprecated public MediaStore.Images.Thumbnails(); method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long); method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long); - method public static android.net.Uri getContentUri(String); + method @Deprecated public static android.net.Uri getContentUri(String); method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); - method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]); - method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]); - method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]); + method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]); + method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]); + method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]); field @Deprecated public static final String DATA = "_data"; - field public static final String DEFAULT_SORT_ORDER = "image_id ASC"; - field public static final android.net.Uri EXTERNAL_CONTENT_URI; - field public static final int FULL_SCREEN_KIND = 2; // 0x2 - field public static final String HEIGHT = "height"; - field public static final String IMAGE_ID = "image_id"; - field public static final android.net.Uri INTERNAL_CONTENT_URI; - field public static final String KIND = "kind"; - field public static final int MICRO_KIND = 3; // 0x3 - field public static final int MINI_KIND = 1; // 0x1 - field public static final String THUMB_DATA = "thumb_data"; - field public static final String WIDTH = "width"; + field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC"; + field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI; + field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2 + field @Deprecated public static final String HEIGHT = "height"; + field @Deprecated public static final String IMAGE_ID = "image_id"; + field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI; + field @Deprecated public static final String KIND = "kind"; + field @Deprecated public static final int MICRO_KIND = 3; // 0x3 + field @Deprecated public static final int MINI_KIND = 1; // 0x1 + field @Deprecated public static final String THUMB_DATA = "thumb_data"; + field @Deprecated public static final String WIDTH = "width"; } public static interface MediaStore.MediaColumns extends android.provider.BaseColumns { @@ -38451,24 +38533,24 @@ package android.provider { field public static final android.net.Uri INTERNAL_CONTENT_URI; } - public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns { - ctor public MediaStore.Video.Thumbnails(); + @Deprecated public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns { + ctor @Deprecated public MediaStore.Video.Thumbnails(); method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long); method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long); - method public static android.net.Uri getContentUri(String); + method @Deprecated public static android.net.Uri getContentUri(String); method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); field @Deprecated public static final String DATA = "_data"; - field public static final String DEFAULT_SORT_ORDER = "video_id ASC"; - field public static final android.net.Uri EXTERNAL_CONTENT_URI; - field public static final int FULL_SCREEN_KIND = 2; // 0x2 - field public static final String HEIGHT = "height"; - field public static final android.net.Uri INTERNAL_CONTENT_URI; - field public static final String KIND = "kind"; - field public static final int MICRO_KIND = 3; // 0x3 - field public static final int MINI_KIND = 1; // 0x1 - field public static final String VIDEO_ID = "video_id"; - field public static final String WIDTH = "width"; + field @Deprecated public static final String DEFAULT_SORT_ORDER = "video_id ASC"; + field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI; + field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2 + field @Deprecated public static final String HEIGHT = "height"; + field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI; + field @Deprecated public static final String KIND = "kind"; + field @Deprecated public static final int MICRO_KIND = 3; // 0x3 + field @Deprecated public static final int MINI_KIND = 1; // 0x1 + field @Deprecated public static final String VIDEO_ID = "video_id"; + field @Deprecated public static final String WIDTH = "width"; } public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns { @@ -38666,6 +38748,7 @@ package android.provider { public static final class Settings.Panel { field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final String ACTION_NFC = "android.settings.panel.action.NFC"; field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; } @@ -39049,6 +39132,7 @@ package android.provider { field public static final String CONTENT_ID = "cid"; field public static final String CONTENT_LOCATION = "cl"; field public static final String CONTENT_TYPE = "ct"; + field public static final android.net.Uri CONTENT_URI; field public static final String CT_START = "ctt_s"; field public static final String CT_TYPE = "ctt_t"; field public static final String FILENAME = "fn"; @@ -41310,6 +41394,22 @@ package android.service.media { package android.service.notification { + public final class Adjustment implements android.os.Parcelable { + ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int); + method public int describeContents(); + method public CharSequence getExplanation(); + method public String getKey(); + method public String getPackage(); + method public android.os.Bundle getSignals(); + method public int getUser(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; + field public static final String KEY_IMPORTANCE = "key_importance"; + field public static final String KEY_SMART_ACTIONS = "key_smart_actions"; + field public static final String KEY_SMART_REPLIES = "key_smart_replies"; + field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; + } + public final class Condition implements android.os.Parcelable { ctor public Condition(android.net.Uri, String, int); ctor public Condition(android.net.Uri, String, String, String, int, int, int); @@ -41356,6 +41456,24 @@ package android.service.notification { field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { + ctor public NotificationAssistantService(); + method public final void adjustNotification(android.service.notification.Adjustment); + method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); + method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int); + method public final android.os.IBinder onBind(android.content.Intent); + method public void onNotificationDirectReplied(@NonNull String); + method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); + method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); + method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); + method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); + method public void onNotificationsSeen(java.util.List<java.lang.String>); + method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); + field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; + field public static final int SOURCE_FROM_APP = 0; // 0x0 + field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 + } + public abstract class NotificationListenerService extends android.app.Service { ctor public NotificationListenerService(); method public final void cancelAllNotifications(); @@ -41436,6 +41554,8 @@ package android.service.notification { method public long getLastAudiblyAlertedMillis(); method public String getOverrideGroupKey(); method public int getRank(); + method public java.util.List<android.app.Notification.Action> getSmartActions(); + method public java.util.List<java.lang.CharSequence> getSmartReplies(); method public int getSuppressedVisualEffects(); method public int getUserSentiment(); method public boolean isAmbient(); @@ -41454,6 +41574,37 @@ package android.service.notification { field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR; } + public final class NotificationStats implements android.os.Parcelable { + ctor public NotificationStats(); + method public int describeContents(); + method public int getDismissalSentiment(); + method public int getDismissalSurface(); + method public boolean hasDirectReplied(); + method public boolean hasExpanded(); + method public boolean hasInteracted(); + method public boolean hasSeen(); + method public boolean hasSnoozed(); + method public boolean hasViewedSettings(); + method public void setDirectReplied(); + method public void setDismissalSentiment(int); + method public void setDismissalSurface(int); + method public void setExpanded(); + method public void setSeen(); + method public void setSnoozed(); + method public void setViewedSettings(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR; + field public static final int DISMISSAL_AOD = 2; // 0x2 + field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff + field public static final int DISMISSAL_OTHER = 0; // 0x0 + field public static final int DISMISSAL_PEEK = 1; // 0x1 + field public static final int DISMISSAL_SHADE = 3; // 0x3 + field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0 + field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1 + field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2 + field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18 + } + public class StatusBarNotification implements android.os.Parcelable { ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long); ctor public StatusBarNotification(android.os.Parcel); @@ -41648,6 +41799,7 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); + method public final void clearTranscription(boolean); method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method public int getDisabledShowContext(); method public static boolean isActiveService(android.content.Context, android.content.ComponentName); @@ -41657,9 +41809,15 @@ package android.service.voice { method public void onReady(); method public void onShutdown(); method public void setDisabledShowContext(int); + method public final void setTranscription(@NonNull String); + method public final void setVoiceState(int); method public void showSession(android.os.Bundle, int); field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; field public static final String SERVICE_META_DATA = "android.voice_interaction"; + field public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1; // 0x1 + field public static final int VOICE_STATE_FULFILLING = 3; // 0x3 + field public static final int VOICE_STATE_LISTENING = 2; // 0x2 + field public static final int VOICE_STATE_NONE = 0; // 0x0 } public class VoiceInteractionSession implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback { @@ -43020,6 +43178,15 @@ package android.telecom { method public abstract void onScreenCall(@NonNull android.telecom.Call.Details); method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification); method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse); + field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; + field public static final int CALL_DURATION_LONG = 4; // 0x4 + field public static final int CALL_DURATION_MEDIUM = 3; // 0x3 + field public static final int CALL_DURATION_SHORT = 2; // 0x2 + field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1 + field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; + field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; + field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; + field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService"; } @@ -43625,6 +43792,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String); method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); @@ -43872,7 +44040,9 @@ package android.telephony { field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; @@ -44173,6 +44343,7 @@ package android.telephony { public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); method public int getAsuLevel(); + method public int getBitErrorRate(); method public int getDbm(); method public int getLevel(); method public int getTimingAdvance(); @@ -44474,22 +44645,23 @@ package android.telephony { public class SignalStrength implements android.os.Parcelable { method public int describeContents(); - method public int getCdmaDbm(); - method public int getCdmaEcio(); + method @Deprecated public int getCdmaDbm(); + method @Deprecated public int getCdmaEcio(); method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths(); - method public int getEvdoDbm(); - method public int getEvdoEcio(); - method public int getEvdoSnr(); - method public int getGsmBitErrorRate(); - method public int getGsmSignalStrength(); + method @Deprecated public int getEvdoDbm(); + method @Deprecated public int getEvdoEcio(); + method @Deprecated public int getEvdoSnr(); + method @Deprecated public int getGsmBitErrorRate(); + method @Deprecated public int getGsmSignalStrength(); method public int getLevel(); - method public boolean isGsm(); + method @Deprecated public boolean isGsm(); method public void writeToParcel(android.os.Parcel, int); field public static final int INVALID = 2147483647; // 0x7fffffff } public final class SmsManager { method public String createAppSpecificSmsToken(android.app.PendingIntent); + method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent); method public java.util.ArrayList<java.lang.String> divideMessage(String); method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent); method public android.os.Bundle getCarrierConfigValues(); @@ -44635,6 +44807,7 @@ package android.telephony { method public String getNumber(); method public int getSimSlotIndex(); method public int getSubscriptionId(); + method public int getSubscriptionType(); method public boolean isEmbedded(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); @@ -44685,6 +44858,8 @@ package android.telephony { field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff + field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 + field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 } public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener { @@ -48393,6 +48568,7 @@ package android.view { method public String getName(); method @Deprecated public int getOrientation(); method @Deprecated public int getPixelFormat(); + method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace(); method public long getPresentationDeadlineNanos(); method public void getRealMetrics(android.util.DisplayMetrics); method public void getRealSize(android.graphics.Point); @@ -52597,6 +52773,7 @@ package android.view.contentcapture { public static final class ContentCaptureContext.Builder { ctor public ContentCaptureContext.Builder(); method public android.view.contentcapture.ContentCaptureContext build(); + method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setAction(@NonNull String); method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setUri(@NonNull android.net.Uri); } @@ -52613,12 +52790,12 @@ package android.view.contentcapture { method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext); method public final void destroy(); method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); - method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, int); - method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, int); + method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long); + method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long); method public final void notifyViewAppeared(@NonNull android.view.ViewStructure); method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId); method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence, int); - method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull int[]); + method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]); } public final class ContentCaptureSessionId implements android.os.Parcelable { @@ -52957,8 +53134,8 @@ package android.view.inputmethod { method @Deprecated public boolean isWatchingCursor(android.view.View); method public void restartInput(android.view.View); method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle); - method public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype); + method @Deprecated public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]); + method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype); method @Deprecated public void setInputMethod(android.os.IBinder, String); method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype); method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder); @@ -52976,7 +53153,7 @@ package android.view.inputmethod { method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo); method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText); method public void updateSelection(android.view.View, int, int, int, int); - method public void viewClicked(android.view.View); + method @Deprecated public void viewClicked(android.view.View); field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1 field public static final int HIDE_NOT_ALWAYS = 2; // 0x2 field public static final int RESULT_HIDDEN = 3; // 0x3 @@ -53055,6 +53232,16 @@ package android.view.inspector { ctor public InspectionCompanion.UninitializedPropertyMapException(); } + public final class IntEnumMapping { + method @Nullable public String get(int); + } + + public static final class IntEnumMapping.Builder { + ctor public IntEnumMapping.Builder(); + method @NonNull public android.view.inspector.IntEnumMapping.Builder addValue(@NonNull String, int); + method @NonNull public android.view.inspector.IntEnumMapping build(); + } + public final class IntFlagMapping { method @NonNull public java.util.Set<java.lang.String> get(int); } @@ -53075,7 +53262,7 @@ package android.view.inspector { method public int mapFloat(@NonNull String, @AttrRes int); method public int mapGravity(@NonNull String, @AttrRes int); method public int mapInt(@NonNull String, @AttrRes int); - method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.util.SparseArray<java.lang.String>); + method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntEnumMapping); method public int mapIntFlag(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntFlagMapping); method public int mapLong(@NonNull String, @AttrRes int); method public int mapObject(@NonNull String, @AttrRes int); @@ -53160,8 +53347,8 @@ package android.view.textclassifier { method @Nullable public CharSequence getText(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; - field public static final android.app.Person PERSON_USER_LOCAL; - field public static final android.app.Person PERSON_USER_REMOTE; + field public static final android.app.Person PERSON_USER_OTHERS; + field public static final android.app.Person PERSON_USER_SELF; } public static final class ConversationActions.Message.Builder { @@ -53312,6 +53499,7 @@ package android.view.textclassifier { public final class TextClassificationManager { method @NonNull public android.view.textclassifier.TextClassifier createTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext); + method @NonNull public android.view.textclassifier.TextClassifier getLocalTextClassifier(); method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier(); method public void setTextClassificationSessionFactory(@Nullable android.view.textclassifier.TextClassificationSessionFactory); method public void setTextClassifier(@Nullable android.view.textclassifier.TextClassifier); @@ -53389,7 +53577,7 @@ package android.view.textclassifier { public final class TextClassifierEvent implements android.os.Parcelable { method public int describeContents(); method @NonNull public int[] getActionIndices(); - method @Nullable public String getEntityType(); + method @NonNull public String[] getEntityTypes(); method public int getEventCategory(); method @Nullable public android.view.textclassifier.TextClassificationContext getEventContext(); method public int getEventIndex(); @@ -53402,6 +53590,7 @@ package android.view.textclassifier { method public int getRelativeWordEndIndex(); method public int getRelativeWordStartIndex(); method @Nullable public String getResultId(); + method public float getScore(); method public void writeToParcel(android.os.Parcel, int); field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3 field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4 @@ -53436,7 +53625,7 @@ package android.view.textclassifier { ctor public TextClassifierEvent.Builder(int, int); method @NonNull public android.view.textclassifier.TextClassifierEvent build(); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(@NonNull int...); - method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(@Nullable String); + method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityTypes(@NonNull java.lang.String...); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(@Nullable android.view.textclassifier.TextClassificationContext); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long); @@ -53447,6 +53636,7 @@ package android.view.textclassifier { method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setResultId(@Nullable String); + method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setScore(float); } public final class TextLanguage implements android.os.Parcelable { @@ -56823,7 +57013,7 @@ package android.widget { method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); method public boolean isFallbackLineSpacing(); - method public final boolean isHorizontallyScrolling(); + method public final boolean isHorizontallyScrollable(); method public boolean isInputMethodTarget(); method public boolean isSingleLine(); method public boolean isSuggestionsEnabled(); @@ -62197,20 +62387,20 @@ package java.nio { method public abstract Object array(); method public abstract int arrayOffset(); method public final int capacity(); - method public final java.nio.Buffer clear(); - method public final java.nio.Buffer flip(); + method public java.nio.Buffer clear(); + method public java.nio.Buffer flip(); method public abstract boolean hasArray(); method public final boolean hasRemaining(); method public abstract boolean isDirect(); method public abstract boolean isReadOnly(); method public final int limit(); - method public final java.nio.Buffer limit(int); - method public final java.nio.Buffer mark(); + method public java.nio.Buffer limit(int); + method public java.nio.Buffer mark(); method public final int position(); - method public final java.nio.Buffer position(int); + method public java.nio.Buffer position(int); method public final int remaining(); - method public final java.nio.Buffer reset(); - method public final java.nio.Buffer rewind(); + method public java.nio.Buffer reset(); + method public java.nio.Buffer rewind(); } public class BufferOverflowException extends java.lang.RuntimeException { diff --git a/api/removed.txt b/api/removed.txt index e23222719ea9..9f4b0416246d 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -239,8 +239,8 @@ package android.graphics { } public class Picture { - method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream); - method @Deprecated public void writeToStream(java.io.OutputStream); + method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream); + method @Deprecated public void writeToStream(@NonNull java.io.OutputStream); } @Deprecated public class PixelXorXfermode extends android.graphics.Xfermode { diff --git a/api/system-current.txt b/api/system-current.txt index 56033a9f800a..e5ae9a7002b4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -244,6 +244,7 @@ package android { } public static final class R.style { + field public static final int Theme_DeviceDefault_DocumentsUI = 16974562; // 0x10302e2 field public static final int Theme_Leanback_FormWizard = 16974544; // 0x10302d0 } @@ -507,11 +508,13 @@ package android.app { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long); + method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException; field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; @@ -882,8 +885,8 @@ package android.app.contentsuggestions { public final class ClassificationsRequest implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getExtras(); - method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections(); + method @Nullable public android.os.Bundle getExtras(); + method @NonNull public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR; } @@ -897,8 +900,8 @@ package android.app.contentsuggestions { public final class ContentClassification implements android.os.Parcelable { ctor public ContentClassification(@NonNull String, @NonNull android.os.Bundle); method public int describeContents(); - method public android.os.Bundle getExtras(); - method public String getId(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR; } @@ -906,8 +909,8 @@ package android.app.contentsuggestions { public final class ContentSelection implements android.os.Parcelable { ctor public ContentSelection(@NonNull String, @NonNull android.os.Bundle); method public int describeContents(); - method public android.os.Bundle getExtras(); - method public String getId(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR; } @@ -929,8 +932,8 @@ package android.app.contentsuggestions { public final class SelectionsRequest implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getExtras(); - method public android.graphics.Point getInterestPoint(); + method @Nullable public android.os.Bundle getExtras(); + method @Nullable public android.graphics.Point getInterestPoint(); method public int getTaskId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR; @@ -1052,6 +1055,7 @@ package android.app.role { method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>); + field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; } public interface RoleManagerCallback { @@ -1095,6 +1099,8 @@ package android.app.usage { public static final class UsageEvents.Event { method public int getInstanceId(); method public String getNotificationChannelId(); + method @Nullable public String getTaskRootClassName(); + method @Nullable public String getTaskRootPackageName(); field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc field public static final int NOTIFICATION_SEEN = 10; // 0xa field public static final int SLICE_PINNED = 14; // 0xe @@ -1109,6 +1115,8 @@ package android.app.usage { public final class UsageStatsManager { method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets(); + method public int getUsageSource(); + method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent); method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String); @@ -1116,6 +1124,7 @@ package android.app.usage { method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String); method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int); method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>); + method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int); method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle); @@ -1124,6 +1133,8 @@ package android.app.usage { field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED"; field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5 field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32 + field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2 + field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1 } } @@ -1136,19 +1147,50 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method public boolean isBleScanAlwaysAvailable(); method public boolean isLeEnabled(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } + public abstract static class BluetoothAdapter.MetadataListener { + ctor public BluetoothAdapter.MetadataListener(); + method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); + } + public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 + field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED"; + field public static final int METADATA_COMPANION_APP = 4; // 0x4 + field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 + field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 + field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_MAIN_ICON = 5; // 0x5 + field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 + field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 + field public static final int METADATA_MODEL_NAME = 1; // 0x1 + field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2 + field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc + field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf + field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa + field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd + field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb + field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe + field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { @@ -1263,13 +1305,13 @@ package android.content { field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS"; field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES"; field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION"; - field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED"; field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE"; field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS"; field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; + field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED"; field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; @@ -1645,33 +1687,27 @@ package android.content.pm.permission { package android.content.rollback { public final class PackageRollbackInfo implements android.os.Parcelable { - ctor public PackageRollbackInfo(String, android.content.rollback.PackageRollbackInfo.PackageVersion, android.content.rollback.PackageRollbackInfo.PackageVersion); method public int describeContents(); + method public String getPackageName(); + method public android.content.pm.VersionedPackage getVersionRolledBackFrom(); + method public android.content.pm.VersionedPackage getVersionRolledBackTo(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR; - field public final android.content.rollback.PackageRollbackInfo.PackageVersion higherVersion; - field public final android.content.rollback.PackageRollbackInfo.PackageVersion lowerVersion; - field public final String packageName; - } - - public static class PackageRollbackInfo.PackageVersion { - ctor public PackageRollbackInfo.PackageVersion(long); - field public final long versionCode; } public final class RollbackInfo implements android.os.Parcelable { method public int describeContents(); + method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages(); + method public int getRollbackId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR; - field public final android.content.rollback.PackageRollbackInfo targetPackage; } public final class RollbackManager { - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks(); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks(); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData(); } @@ -1777,9 +1813,16 @@ package android.hardware.display { } public final class ColorDisplayManager { + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getNightDisplayAutoMode(); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities(); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayAutoMode(int); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomEndTime(@NonNull java.time.LocalTime); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomStartTime(@NonNull java.time.LocalTime); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int); + field public static final int AUTO_MODE_CUSTOM_TIME = 1; // 0x1 + field public static final int AUTO_MODE_DISABLED = 0; // 0x0 + field public static final int AUTO_MODE_TWILIGHT = 2; // 0x2 field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2 field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4 field public static final int CAPABILITY_NONE = 0; // 0x0 @@ -1811,9 +1854,15 @@ package android.hardware.hdmi { public final class HdmiControlManager { method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener); method @Nullable public android.hardware.hdmi.HdmiClient getClient(int); + method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList(); + method public int getPhysicalAddress(); method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient(); + method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient(); + method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo); + method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener); + method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean); field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; field public static final int AVR_VOLUME_MUTED = 101; // 0x65 @@ -1903,6 +1952,9 @@ package android.hardware.hdmi { field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa } + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult { + } + public static interface HdmiControlManager.HotplugEventListener { method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent); } @@ -2029,6 +2081,16 @@ package android.hardware.hdmi { public abstract static class HdmiRecordSources.RecordSource { } + public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient { + method public int getDeviceType(); + method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener); + method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener); + } + + public static interface HdmiSwitchClient.OnSelectListener { + method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int); + } + public class HdmiTimerRecordSources { method public static boolean checkTimerRecordSource(int, byte[]); method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int); @@ -3353,6 +3415,15 @@ package android.media { method public void stop(); } + public final class Session2Token implements android.os.Parcelable { + ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle); + method public void destroy(); + method @NonNull public android.os.Bundle getExtras(); + method public int getPid(); + method public boolean isDestroyed(); + field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + public static class SubtitleData.Builder { ctor public SubtitleData.Builder(); ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData); @@ -3367,6 +3438,15 @@ package android.media { method @NonNull public android.media.TimedMetaData.Builder setTimedMetaData(long, @NonNull byte[]); } + public abstract class VolumeProvider { + method public void setCallback(android.media.VolumeProvider.Callback); + } + + public abstract static class VolumeProvider.Callback { + ctor public VolumeProvider.Callback(); + method public abstract void onVolumeChanged(android.media.VolumeProvider); + } + } package android.media.audiopolicy { @@ -3542,6 +3622,10 @@ package android.media.session { method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate); } + public static final class MediaSession.Token implements android.os.Parcelable { + method public android.media.session.ControllerLink getControllerLink(); + } + public final class MediaSessionEngine implements java.lang.AutoCloseable { ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int); method public void close(); @@ -3981,6 +4065,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); @@ -4001,6 +4086,10 @@ package android.net { method public void onTetheringStarted(); } + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(java.net.InetAddress, int); + } + public final class IpSecManager { method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; @@ -4033,6 +4122,7 @@ package android.net { } public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(java.net.InetAddress, int, int, int); ctor public LinkAddress(java.net.InetAddress, int); ctor public LinkAddress(String); method public boolean isGlobalPreferred(); @@ -4043,9 +4133,12 @@ package android.net { public final class LinkProperties implements android.os.Parcelable { ctor public LinkProperties(); + ctor public LinkProperties(android.net.LinkProperties); method public boolean addDnsServer(java.net.InetAddress); method public boolean addRoute(android.net.RouteInfo); method public void clear(); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method public java.util.List<java.net.InetAddress> getPcscfServers(); method public String getTcpBufferSizes(); method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); method public boolean hasGlobalIPv6Address(); @@ -4063,6 +4156,8 @@ package android.net { method public void setInterfaceName(String); method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>); method public void setMtu(int); + method public void setNat64Prefix(android.net.IpPrefix); + method public void setPcscfServers(java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(String); method public void setUsePrivateDns(boolean); @@ -4117,6 +4212,7 @@ package android.net { } public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int); method public int getType(); field public static final int RTN_THROW = 9; // 0x9 field public static final int RTN_UNICAST = 1; // 0x1 @@ -4250,6 +4346,7 @@ package android.net.metrics { } public class IpConnectivityLog { + ctor public IpConnectivityLog(); method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); @@ -4298,6 +4395,20 @@ package android.net.metrics { field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3 } + public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { + } + + public static class RaEvent.Builder { + ctor public RaEvent.Builder(); + method public android.net.metrics.RaEvent build(); + method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long); + } + public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { method public static String getProbeName(int); field public static final int DNS_FAILURE = 0; // 0x0 @@ -4569,6 +4680,7 @@ package android.net.wifi { } public class WifiManager { + method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener); @@ -4576,14 +4688,14 @@ package android.net.wifi { method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>); - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); method public boolean isDeviceToDeviceRttSupported(); method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler); + method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); @@ -4592,7 +4704,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback); + method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int); field public static final int CHANGE_REASON_ADDED = 0; // 0x0 field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2 field public static final int CHANGE_REASON_REMOVED = 1; // 0x1 @@ -4628,17 +4740,8 @@ package android.net.wifi { method public void onSuccess(); } - public static interface WifiManager.NetworkRequestMatchCallback { - method public void onAbort(); - method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>); - method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback); - method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration); - method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration); - } - - public static interface WifiManager.NetworkRequestUserSelectionCallback { - method public void reject(); - method public void select(@NonNull android.net.wifi.WifiConfiguration); + public static interface WifiManager.WifiUsabilityStatsListener { + method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry); } public class WifiNetworkConnectionStatistics implements android.os.Parcelable { @@ -4770,6 +4873,31 @@ package android.net.wifi { field @Deprecated public int unchangedSampleSize; } + public final class WifiUsabilityStatsEntry implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry> CREATOR; + field public final int linkSpeedMbps; + field public final int rssi; + field public final long timeStampMs; + field public final long totalBackgroundScanTimeMs; + field public final long totalBeaconRx; + field public final long totalCcaBusyFreqTimeMs; + field public final long totalHotspot2ScanTimeMs; + field public final long totalNanScanTimeMs; + field public final long totalPnoScanTimeMs; + field public final long totalRadioOnFreqTimeMs; + field public final long totalRadioOnTimeMs; + field public final long totalRadioRxTimeMs; + field public final long totalRadioTxTimeMs; + field public final long totalRoamScanTimeMs; + field public final long totalRxSuccess; + field public final long totalScanTimeMs; + field public final long totalTxBad; + field public final long totalTxRetries; + field public final long totalTxSuccess; + } + } package android.net.wifi.aware { @@ -4915,6 +5043,7 @@ package android.nfc { package android.os { public class BatteryManager { + method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int); field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS"; field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; } @@ -5360,8 +5489,9 @@ package android.permission { method public final android.os.IBinder onBind(android.content.Intent); method public abstract int onCountPermissionApps(@NonNull java.util.List<java.lang.String>, boolean, boolean); method @NonNull public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String); + method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long); method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream); - method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long); + method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String); method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String); method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String); field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService"; @@ -5405,7 +5535,6 @@ package android.permissionpresenterservice { method @Deprecated public final void attachBaseContext(android.content.Context); method @Deprecated public final android.os.IBinder onBind(android.content.Intent); method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String); - method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String); field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService"; } @@ -5525,10 +5654,45 @@ package android.provider { field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; } + public static interface DeviceConfig.ActivityManager { + field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; + field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; + field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + field public static final String KEY_USE_COMPACTION = "use_compaction"; + field public static final String NAMESPACE = "activity_manager"; + } + + public static interface DeviceConfig.FsiBoot { + field public static final String NAMESPACE = "fsi_boot"; + field public static final String OOB_ENABLED = "oob_enabled"; + field public static final String OOB_WHITELIST = "oob_whitelist"; + } + + public static interface DeviceConfig.IntelligenceAttention { + field public static final String NAMESPACE = "intelligence_attention"; + field public static final String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled"; + field public static final String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings"; + } + public static interface DeviceConfig.OnPropertyChangedListener { method public void onPropertyChanged(String, String, String); } + public static interface DeviceConfig.Storage { + field public static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled"; + field public static final String NAMESPACE = "storage"; + } + + public static interface DeviceConfig.Telephony { + field public static final String NAMESPACE = "telephony"; + field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer"; + field public static final String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration"; + } + public final class DocumentsContract { method public static boolean isManageMode(android.net.Uri); method public static android.net.Uri setManageMode(android.net.Uri); @@ -5710,6 +5874,7 @@ package android.provider { field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications"; field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications"; field public static final String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count"; + field public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = "theme_customization_overlay_packages"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0 @@ -5985,10 +6150,7 @@ package android.service.autofill.augmented { method public int getTaskId(); } - public final class FillResponse implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR; + public final class FillResponse { } public static final class FillResponse.Builder { @@ -6002,7 +6164,6 @@ package android.service.autofill.augmented { ctor public FillWindow(); method public void destroy(); method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long); - field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L } public abstract class PresentationParams { @@ -6164,12 +6325,15 @@ package android.service.euicc { method public abstract int onSwitchToSubscription(int, @Nullable String, boolean); method public abstract int onUpdateSubscriptionNickname(int, String, String); field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"; + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE"; field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM"; field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES"; field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI"; field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService"; field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES"; @@ -6225,77 +6389,6 @@ package android.service.euicc { package android.service.notification { - public final class Adjustment implements android.os.Parcelable { - ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int); - ctor protected Adjustment(android.os.Parcel); - method public int describeContents(); - method public CharSequence getExplanation(); - method public String getKey(); - method public String getPackage(); - method public android.os.Bundle getSignals(); - method public int getUser(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; - field public static final String KEY_IMPORTANCE = "key_importance"; - field public static final String KEY_PEOPLE = "key_people"; - field public static final String KEY_SMART_ACTIONS = "key_smart_actions"; - field public static final String KEY_SMART_REPLIES = "key_smart_replies"; - field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; - field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; - } - - public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { - ctor public NotificationAssistantService(); - method public final void adjustNotification(android.service.notification.Adjustment); - method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); - method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int); - method public final android.os.IBinder onBind(android.content.Intent); - method public void onNotificationDirectReplied(@NonNull String); - method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); - method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); - method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); - method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); - method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String); - method public void onNotificationsSeen(java.util.List<java.lang.String>); - method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); - method public final void unsnoozeNotification(String); - field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; - field public static final int SOURCE_FROM_APP = 0; // 0x0 - field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 - } - - public final class NotificationStats implements android.os.Parcelable { - ctor public NotificationStats(); - ctor protected NotificationStats(android.os.Parcel); - method public int describeContents(); - method public int getDismissalSentiment(); - method public int getDismissalSurface(); - method public boolean hasDirectReplied(); - method public boolean hasExpanded(); - method public boolean hasInteracted(); - method public boolean hasSeen(); - method public boolean hasSnoozed(); - method public boolean hasViewedSettings(); - method public void setDirectReplied(); - method public void setDismissalSentiment(int); - method public void setDismissalSurface(int); - method public void setExpanded(); - method public void setSeen(); - method public void setSnoozed(); - method public void setViewedSettings(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR; - field public static final int DISMISSAL_AOD = 2; // 0x2 - field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff - field public static final int DISMISSAL_OTHER = 0; // 0x0 - field public static final int DISMISSAL_PEEK = 1; // 0x1 - field public static final int DISMISSAL_SHADE = 3; // 0x3 - field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0 - field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1 - field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2 - field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18 - } - public final class SnoozeCriterion implements android.os.Parcelable { ctor public SnoozeCriterion(String, CharSequence, CharSequence); ctor protected SnoozeCriterion(android.os.Parcel); @@ -7536,10 +7629,13 @@ package android.telephony { public class SubscriptionManager { method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int); method public void requestEmbeddedSubscriptionInfoListRefresh(); method public void requestEmbeddedSubscriptionInfoListRefresh(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean); field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2 @@ -7611,6 +7707,7 @@ package android.telephony { method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState(); method public int getSimApplicationState(); method public int getSimCardState(); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo(); @@ -7913,9 +8010,12 @@ package android.telephony.euicc { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED"; - field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4 field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2 field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1 field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3 @@ -7926,7 +8026,10 @@ package android.telephony.euicc { field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3 field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS"; + field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION"; + field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; } @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus { @@ -8142,6 +8245,16 @@ package android.telephony.ims { field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants; } + public class ImsException extends java.lang.Exception { + ctor public ImsException(@Nullable String); + ctor public ImsException(@Nullable String, int); + ctor public ImsException(@Nullable String, int, Throwable); + method public int getCode(); + field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1 + field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2 + } + public final class ImsExternalCallState implements android.os.Parcelable { ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean); method public int describeContents(); @@ -8159,7 +8272,7 @@ package android.telephony.ims { } public class ImsMmTelManager { - method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled(); @@ -8168,8 +8281,8 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int); @@ -8604,13 +8717,21 @@ package android.telephony.ims { } public class ProvisioningManager { - method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String); + method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b + field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 + field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 + field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; + field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; } public static class ProvisioningManager.Callback { @@ -9037,9 +9158,22 @@ package android.view.accessibility { } +package android.view.autofill { + + public final class AutofillManager { + method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities(); + method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages(); + method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean); + method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>); + method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean); + } + +} + package android.view.contentcapture { public final class ContentCaptureContext implements android.os.Parcelable { + method @Nullable public String getAction(); method @Nullable public android.content.ComponentName getActivityComponent(); method public int getDisplayId(); method @Nullable public android.os.Bundle getExtras(); diff --git a/api/test-current.txt b/api/test-current.txt index d08983103ce8..ae29e4952108 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6,14 +6,20 @@ package android { field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; + field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; } + public static final class R.array { + field public static final int config_defaultRoleHolders = 17235974; // 0x1070006 + } + } package android.animation { @@ -332,6 +338,7 @@ package android.app.role { method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String); method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback); + field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; } public interface RoleManagerCallback { @@ -352,6 +359,10 @@ package android.app.usage { method public boolean isReservedSupported(@NonNull java.util.UUID); } + public final class UsageStatsManager { + method public void forceUsageSourceSettingRead(); + } + } package android.bluetooth { @@ -772,6 +783,11 @@ package android.media.audiofx { field public static final java.util.UUID EFFECT_TYPE_NULL; } + public static class AudioEffect.Descriptor { + ctor public AudioEffect.Descriptor(android.os.Parcel); + method public void writeToParcel(android.os.Parcel); + } + public static interface AudioEffect.OnParameterChangeListener { method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]); } @@ -793,11 +809,16 @@ package android.net { field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; } + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(java.net.InetAddress, int); + } + public final class IpSecManager { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(java.net.InetAddress, int, int, int); method public boolean isGlobalPreferred(); method public boolean isIPv4(); method public boolean isIPv6(); @@ -805,7 +826,10 @@ package android.net { } public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(android.net.LinkProperties); method public boolean addDnsServer(java.net.InetAddress); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method public java.util.List<java.net.InetAddress> getPcscfServers(); method public String getTcpBufferSizes(); method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); method public boolean hasGlobalIPv6Address(); @@ -817,6 +841,8 @@ package android.net { method public boolean isReachable(java.net.InetAddress); method public boolean removeDnsServer(java.net.InetAddress); method public boolean removeRoute(android.net.RouteInfo); + method public void setNat64Prefix(android.net.IpPrefix); + method public void setPcscfServers(java.util.Collection<java.net.InetAddress>); method public void setPrivateDnsServerName(@Nullable String); method public void setTcpBufferSizes(String); method public void setUsePrivateDns(boolean); @@ -834,6 +860,7 @@ package android.net { } public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int); method public int getType(); field public static final int RTN_THROW = 9; // 0x9 field public static final int RTN_UNICAST = 1; // 0x1 @@ -920,6 +947,7 @@ package android.net.metrics { } public class IpConnectivityLog { + ctor public IpConnectivityLog(); method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); @@ -968,6 +996,20 @@ package android.net.metrics { field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3 } + public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { + } + + public static class RaEvent.Builder { + ctor public RaEvent.Builder(); + method public android.net.metrics.RaEvent build(); + method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long); + method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long); + method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long); + } + public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { method public static String getProbeName(int); field public static final int DNS_FAILURE = 0; // 0x0 @@ -1012,6 +1054,10 @@ package android.os { method public static java.io.File getStorageDirectory(); } + public class FileUtils { + method public static boolean contains(java.io.File, java.io.File); + } + public abstract class HwBinder implements android.os.IHwBinder { ctor public HwBinder(); method public static final void configureRpcThreadpool(long, boolean); @@ -1177,6 +1223,10 @@ package android.os { method public boolean hasSingleFileDescriptor(); } + public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { + method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException; + } + public final class PowerManager { method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode(); method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int); @@ -1264,15 +1314,11 @@ package android.os { method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); method public abstract long getDuration(); method protected static int scale(int, float, int); - field public static final int EFFECT_CLICK = 0; // 0x0 - field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1 - field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5 field public static final int EFFECT_POP = 4; // 0x4 field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0 field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1 field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2 field public static final int EFFECT_THUD = 3; // 0x3 - field public static final int EFFECT_TICK = 2; // 0x2 field public static final int[] RINGTONES; } @@ -1448,17 +1494,37 @@ package android.print { package android.provider { + public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns { + field public static final String[] SYNC_WRITABLE_COLUMNS; + } + + public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns { + field public static final String[] SYNC_WRITABLE_COLUMNS; + } + + public final class ContactsContract { + field public static final String HIDDEN_COLUMN_PREFIX = "x_"; + } + public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { field public static final android.net.Uri ENTERPRISE_CONTENT_URI; } + public static final class ContactsContract.PinnedPositions { + field public static final String UNDEMOTE_METHOD = "undemote"; + } + public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns { field public static final android.net.Uri CORP_CONTENT_URI; } public final class MediaStore { - method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; - method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException; + method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException; + field public static final String SCAN_FILE_CALL = "scan_file"; + field public static final String SCAN_VOLUME_CALL = "scan_volume"; } public final class Settings { @@ -1518,6 +1584,10 @@ package android.provider { field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION"; } + public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns { + field public static final String _DATA = "_data"; + } + } package android.security { @@ -1679,10 +1749,7 @@ package android.service.autofill.augmented { method public int getTaskId(); } - public final class FillResponse implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR; + public final class FillResponse { } public static final class FillResponse.Builder { @@ -1696,7 +1763,6 @@ package android.service.autofill.augmented { ctor public FillWindow(); method public void destroy(); method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long); - field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L } public abstract class PresentationParams { @@ -1720,84 +1786,14 @@ package android.service.autofill.augmented { package android.service.notification { - public final class Adjustment implements android.os.Parcelable { - ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int); - ctor protected Adjustment(android.os.Parcel); - method public int describeContents(); - method public CharSequence getExplanation(); - method public String getKey(); - method public String getPackage(); - method public android.os.Bundle getSignals(); - method public int getUser(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; - field public static final String KEY_IMPORTANCE = "key_importance"; - field public static final String KEY_PEOPLE = "key_people"; - field public static final String KEY_SMART_ACTIONS = "key_smart_actions"; - field public static final String KEY_SMART_REPLIES = "key_smart_replies"; - field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; - field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; - } - @Deprecated public abstract class ConditionProviderService extends android.app.Service { method @Deprecated public boolean isBound(); } - public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { - ctor public NotificationAssistantService(); - method public final void adjustNotification(android.service.notification.Adjustment); - method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); - method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int); - method public final android.os.IBinder onBind(android.content.Intent); - method public void onNotificationDirectReplied(@NonNull String); - method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); - method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); - method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); - method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String); - method public void onNotificationsSeen(java.util.List<java.lang.String>); - method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); - method public final void unsnoozeNotification(String); - field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; - field public static final int SOURCE_FROM_APP = 0; // 0x0 - field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 - } - public abstract class NotificationListenerService extends android.app.Service { method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); } - public final class NotificationStats implements android.os.Parcelable { - ctor public NotificationStats(); - ctor protected NotificationStats(android.os.Parcel); - method public int describeContents(); - method public int getDismissalSentiment(); - method public int getDismissalSurface(); - method public boolean hasDirectReplied(); - method public boolean hasExpanded(); - method public boolean hasInteracted(); - method public boolean hasSeen(); - method public boolean hasSnoozed(); - method public boolean hasViewedSettings(); - method public void setDirectReplied(); - method public void setDismissalSentiment(int); - method public void setDismissalSurface(int); - method public void setExpanded(); - method public void setSeen(); - method public void setSnoozed(); - method public void setViewedSettings(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR; - field public static final int DISMISSAL_AOD = 2; // 0x2 - field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff - field public static final int DISMISSAL_OTHER = 0; // 0x0 - field public static final int DISMISSAL_PEEK = 1; // 0x1 - field public static final int DISMISSAL_SHADE = 3; // 0x3 - field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0 - field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1 - field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2 - field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18 - } - public final class SnoozeCriterion implements android.os.Parcelable { ctor public SnoozeCriterion(String, CharSequence, CharSequence); ctor protected SnoozeCriterion(android.os.Parcel); @@ -1866,10 +1862,15 @@ package android.telephony { } public class TelephonyManager { + method public int checkCarrierPrivilegesForPackage(String); method public int getCarrierIdListVersion(); method public boolean isRttSupported(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile(); method public void setCarrierTestOverride(String, String, String, String, String, String, String); + field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe + field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 + field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 + field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff } @@ -2268,6 +2269,10 @@ package android.view { method public static int getLongPressTooltipHideTimeout(); } + public class ViewDebug { + method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>); + } + public interface WindowManager extends android.view.ViewManager { method public default void setShouldShowIme(int, boolean); method public default void setShouldShowSystemDecors(int, boolean); @@ -2328,6 +2333,12 @@ package android.view.autofill { } public final class AutofillManager { + method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities(); + method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages(); + method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean); + method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>); + method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean); + field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1 field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0 } diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 3defdc5467c8..062ba655640e 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -102,7 +102,17 @@ public class Bmgr { String op = nextArg(); Slog.v(TAG, "Running " + op + " for user:" + userId); - if (!isBmgrActive(userId)) { + if (mBmgr == null) { + System.err.println(BMGR_NOT_RUNNING_ERR); + return; + } + + if ("activate".equals(op)) { + doActivateService(userId); + return; + } + + if (!isBackupActive(userId)) { return; } @@ -175,12 +185,7 @@ public class Bmgr { showUsage(); } - boolean isBmgrActive(@UserIdInt int userId) { - if (mBmgr == null) { - System.err.println(BMGR_NOT_RUNNING_ERR); - return false; - } - + boolean isBackupActive(@UserIdInt int userId) { try { if (!mBmgr.isBackupServiceActive(userId)) { System.err.println(BMGR_NOT_RUNNING_ERR); @@ -845,6 +850,27 @@ public class Bmgr { } } + private void doActivateService(int userId) { + String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + + try { + boolean activate = Boolean.parseBoolean(arg); + mBmgr.setBackupServiceActive(userId, activate); + System.out.println( + "Backup service now " + + (activate ? "activated" : "deactivated") + + " for user " + + userId); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; @@ -880,6 +906,7 @@ public class Bmgr { System.err.println(" bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE..."); System.err.println(" bmgr cancel backups"); System.err.println(" bmgr init TRANSPORT..."); + System.err.println(" bmgr activate BOOL"); System.err.println(""); System.err.println("The '--user' option specifies the user on which the operation is run."); System.err.println("It must be the first argument before the operation."); @@ -946,6 +973,11 @@ public class Bmgr { System.err.println(""); System.err.println("The 'init' command initializes the given transports, wiping all data"); System.err.println("from their backing data stores."); + System.err.println(""); + System.err.println("The 'activate' command activates or deactivates the backup service."); + System.err.println("If the argument is 'true' it will be activated, otherwise it will be"); + System.err.println("deactivated. When deactivated, the service will not be running and no"); + System.err.println("operations can be performed until activation."); } private static class BackupMonitor extends IBackupManagerMonitor.Stub { diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp index c455ac0f83af..0c581f3b1a98 100644 --- a/cmds/idmap2/idmap2/Create.cpp +++ b/cmds/idmap2/idmap2/Create.cpp @@ -39,6 +39,7 @@ using android::idmap2::PolicyBitmask; using android::idmap2::PolicyFlags; using android::idmap2::Result; using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::UidHasWriteAccessToPath; bool Create(const std::vector<std::string>& args, std::ostream& out_error) { std::string target_apk_path; @@ -66,6 +67,13 @@ bool Create(const std::vector<std::string>& args, std::ostream& out_error) { return false; } + const uid_t uid = getuid(); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + out_error << "error: uid " << uid << " does not have write access to " << idmap_path + << std::endl; + return false; + } + PolicyBitmask fulfilled_policies = 0; if (auto result = PoliciesToBitmask(policies, out_error)) { fulfilled_policies |= *result; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index a3c752718ee2..f30ce9b08d6e 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -27,6 +27,7 @@ #include "android-base/macros.h" #include "android-base/stringprintf.h" +#include "binder/IPCThreadState.h" #include "utils/String8.h" #include "utils/Trace.h" @@ -38,18 +39,19 @@ #include "idmap2d/Idmap2Service.h" +using android::IPCThreadState; using android::binder::Status; using android::idmap2::BinaryStreamVisitor; using android::idmap2::Idmap; using android::idmap2::IdmapHeader; using android::idmap2::PolicyBitmask; using android::idmap2::Result; +using android::idmap2::utils::kIdmapCacheDir; using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::UidHasWriteAccessToPath; namespace { -constexpr const char* kIdmapCacheDir = "/data/resource-cache"; - Status ok() { return Status::ok(); } @@ -77,7 +79,13 @@ Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path, Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { assert(_aidl_return); + const uid_t uid = IPCThreadState::self()->getCallingUid(); const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + *_aidl_return = false; + return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access", + idmap_path.c_str(), uid)); + } if (unlink(idmap_path.c_str()) != 0) { *_aidl_return = false; return error("failed to unlink " + idmap_path + ": " + strerror(errno)); @@ -118,6 +126,13 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + const uid_t uid = IPCThreadState::self()->getCallingUid(); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss", + idmap_path.c_str(), uid)); + } + const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); if (!target_apk) { return error("failed to load apk " + target_apk_path); @@ -137,7 +152,6 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, } umask(kIdmapFilePermissionMask); - const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h index 5c41c49906cd..3f03236d5e1a 100644 --- a/cmds/idmap2/include/idmap2/FileUtils.h +++ b/cmds/idmap2/include/idmap2/FileUtils.h @@ -17,6 +17,8 @@ #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ +#include <sys/types.h> + #include <functional> #include <memory> #include <string> @@ -24,6 +26,7 @@ namespace android::idmap2::utils { +constexpr const char* kIdmapCacheDir = "/data/resource-cache"; constexpr const mode_t kIdmapFilePermissionMask = 0133; // u=rw,g=r,o=r typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)> @@ -35,6 +38,8 @@ std::unique_ptr<std::string> ReadFile(int fd); std::unique_ptr<std::string> ReadFile(const std::string& path); +bool UidHasWriteAccessToPath(uid_t uid, const std::string& path); + } // namespace android::idmap2::utils #endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp index 0255727fc8c6..a9b68cd6d5d5 100644 --- a/cmds/idmap2/libidmap2/FileUtils.cpp +++ b/cmds/idmap2/libidmap2/FileUtils.cpp @@ -19,12 +19,20 @@ #include <unistd.h> #include <cerrno> +#include <climits> +#include <cstdlib> +#include <cstring> #include <fstream> #include <memory> #include <string> #include <utility> #include <vector> +#include "android-base/file.h" +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "private/android_filesystem_config.h" + #include "idmap2/FileUtils.h" namespace android::idmap2::utils { @@ -77,4 +85,26 @@ std::unique_ptr<std::string> ReadFile(int fd) { return r == 0 ? std::move(str) : nullptr; } +#ifdef __ANDROID__ +bool UidHasWriteAccessToPath(uid_t uid, const std::string& path) { + // resolve symlinks and relative paths; the directories must exist + std::string canonical_path; + if (!base::Realpath(base::Dirname(path), &canonical_path)) { + return false; + } + + const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir); + if (canonical_path == kIdmapCacheDir || + canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) { + // limit access to /data/resource-cache to root and system + return uid == AID_ROOT || uid == AID_SYSTEM; + } + return true; +} +#else +bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path ATTRIBUTE_UNUSED) { + return true; +} +#endif + } // namespace android::idmap2::utils diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp index d9d9a7f829cf..45f84fe341cc 100644 --- a/cmds/idmap2/tests/FileUtilsTests.cpp +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -22,6 +22,8 @@ #include "gtest/gtest.h" #include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "private/android_filesystem_config.h" #include "idmap2/FileUtils.h" @@ -71,4 +73,25 @@ TEST(FileUtilsTests, ReadFile) { close(pipefd[0]); } +#ifdef __ANDROID__ +TEST(FileUtilsTests, UidHasWriteAccessToPath) { + constexpr const char* tmp_path = "/data/local/tmp/test@idmap"; + const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir)); + const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir)); + + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, sneaky_cache_path)); + + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, tmp_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, cache_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, sneaky_cache_path)); + + constexpr const uid_t AID_SOME_APP = AID_SYSTEM + 1; + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SOME_APP, tmp_path)); + ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, cache_path)); + ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, sneaky_cache_path)); +} +#endif + } // namespace android::idmap2::utils diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp index 4334fa60767b..c550eafe5ffe 100644 --- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -38,6 +38,7 @@ #include "gtest/gtest.h" #include "androidfw/PosixUtils.h" +#include "private/android_filesystem_config.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" @@ -69,9 +70,23 @@ void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path, ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \ } while (0) +#ifdef __ANDROID__ +#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 \ + do { \ + const uid_t uid = getuid(); \ + if (uid != AID_ROOT && uid != AID_SYSTEM) { \ + GTEST_SKIP(); \ + } \ + } while (0) +#else +#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 +#endif + } // namespace TEST_F(Idmap2BinaryTests, Create) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -97,6 +112,8 @@ TEST_F(Idmap2BinaryTests, Create) { } TEST_F(Idmap2BinaryTests, Dump) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -144,6 +161,8 @@ TEST_F(Idmap2BinaryTests, Dump) { } TEST_F(Idmap2BinaryTests, Scan) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk"; const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk"; const std::string idmap_static_1_path = @@ -236,6 +255,8 @@ TEST_F(Idmap2BinaryTests, Scan) { } TEST_F(Idmap2BinaryTests, Lookup) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -285,6 +306,8 @@ TEST_F(Idmap2BinaryTests, Lookup) { } TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST"; // missing mandatory options diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 59b2aa6ac3d1..ca104823b7cf 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -74,7 +74,6 @@ cc_defaults { "src/external/SubsystemSleepStatePuller.cpp", "src/external/PowerStatsPuller.cpp", "src/external/ResourceHealthManagerPuller.cpp", - "src/external/ResourceThermalManagerPuller.cpp", "src/external/StatsPullerManager.cpp", "src/external/puller_util.cpp", "src/logd/LogEvent.cpp", @@ -136,7 +135,6 @@ cc_defaults { "android.hardware.power@1.0", "android.hardware.power@1.1", "android.hardware.power.stats@1.0", - "android.hardware.thermal@2.0", "libpackagelistparser", "libsysutils", "libcutils", diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 820da556eaaa..b26c713877db 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -989,6 +989,25 @@ Status StatsService::setDataFetchOperation(int64_t key, return Status::ok(); } +Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, + const String16& packageName, + vector<int64_t>* output) { + ENFORCE_DUMP_AND_USAGE_STATS(packageName); + + IPCThreadState* ipc = IPCThreadState::self(); + mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender); + //TODO: Return the list of configs that are already active + return Status::ok(); +} + +Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) { + ENFORCE_DUMP_AND_USAGE_STATS(packageName); + + IPCThreadState* ipc = IPCThreadState::self(); + mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid()); + return Status::ok(); +} + Status StatsService::removeConfiguration(int64_t key, const String16& packageName) { ENFORCE_DUMP_AND_USAGE_STATS(packageName); @@ -1097,6 +1116,30 @@ hardware::Return<void> StatsService::reportUsbPortOverheatEvent( return hardware::Void(); } +hardware::Return<void> StatsService::reportSpeechDspStat( + const SpeechDspStat& speechDspStat) { + LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), speechDspStat); + mProcessor->OnLogEvent(&event); + + return hardware::Void(); +} + +hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) { + std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName; + if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) { + ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId); + return hardware::Void(); + } + if (reverseDomainName.length() > 50) { + ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str()); + return hardware::Void(); + } + LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom); + mProcessor->OnLogEvent(&event); + + return hardware::Void(); +} + void StatsService::binderDied(const wp <IBinder>& who) { ALOGW("statscompanion service died"); StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index e9b3d4f2f4db..cdff50fcb62e 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -132,6 +132,17 @@ public: const String16& packageName) override; /** + * Binder call to let clients register the active configs changed operation. + */ + virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, + const String16& packageName, + vector<int64_t>* output) override; + + /** + * Binder call to remove the active configs changed operation for the specified package.. + */ + virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override; + /** * Binder call to allow clients to remove the specified configuration. */ virtual Status removeConfiguration(int64_t key, @@ -205,6 +216,17 @@ public: virtual Return<void> reportUsbPortOverheatEvent( const UsbPortOverheatEvent& usbPortOverheatEvent) override; + /** + * Binder call to get Speech DSP state atom. + */ + virtual Return<void> reportSpeechDspStat( + const SpeechDspStat& speechDspStat) override; + + /** + * Binder call to get vendor atom. + */ + virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override; + /** IBinder::DeathRecipient */ virtual void binderDied(const wp<IBinder>& who) override; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1942b2e94f51..620c7a872e67 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -27,12 +27,15 @@ import "frameworks/base/core/proto/android/app/job/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto"; +import "frameworks/base/core/proto/android/debug/enums.proto"; +import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto"; import "frameworks/base/core/proto/android/net/networkcapabilities.proto"; import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto"; import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; +import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; @@ -144,9 +147,9 @@ message Atom { VibratorStateChanged vibrator_state_changed = 84; DeferredJobStatsReported deferred_job_stats_reported = 85; ThermalThrottlingStateChanged thermal_throttling = 86; - FingerprintAcquired fingerprint_acquired = 87; - FingerprintAuthenticated fingerprint_authenticated = 88; - FingerprintErrorOccurred fingerprint_error_occurred = 89; + BiometricAcquired biometric_acquired = 87; + BiometricAuthenticated biometric_authenticated = 88; + BiometricErrorOccurred biometric_error_occurred = 89; Notification notification = 90; BatteryHealthSnapshot battery_health_snapshot = 91; SlowIo slow_io = 92; @@ -161,7 +164,7 @@ message Atom { // Consider removing this if it becomes a problem ServiceStateChanged service_state_changed = 99; ServiceLaunchReported service_launch_reported = 100; - PhenotypeFlagStateChanged phenotype_flag_state_changed = 101; + FlagFlipUpdateOccurred flag_flip_update_occurred = 101; BinaryPushStateChanged binary_push_state_changed = 102; DevicePolicyEvent device_policy_event = 103; DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104; @@ -204,10 +207,15 @@ message Atom { SeOmapiReported se_omapi_reported = 141; BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142; AttentionManagerServiceResultReported attention_manager_service_result_reported = 143; + AdbConnectionChanged adb_connection_changed = 144; + SpeechDspStatReported speech_dsp_stat_reported = 145; + UsbContaminantReported usb_contaminant_reported = 146; + WatchdogRollbackOccurred watchdog_rollback_occurred = 147; + BiometricHalDeathReported biometric_hal_death_reported = 148; } // Pulled events will start at field 10000. - // Next: 10043 + // Next: 10048 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -240,7 +248,7 @@ message Atom { CategorySize category_size = 10028; ProcStats proc_stats = 10029; BatteryVoltage battery_voltage = 10030; - NumFingerprints num_fingerprints = 10031; + NumBiometricsEnrolled num_fingerprints_enrolled = 10031; DiskIo disk_io = 10032; PowerProfile power_profile = 10033; ProcStatsPkgProc proc_stats_pkg_proc = 10034; @@ -255,6 +263,9 @@ message Atom { BatteryLevel battery_level = 10043; BuildInformation build_information = 10044; BatteryCycleCount battery_cycle_count = 10045; + DebugElapsedClock debug_elapsed_clock = 10046; + DebugFailingElapsedClock debug_failing_elapsed_clock = 10047; + NumBiometricsEnrolled num_faces_enrolled = 10048; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -1478,6 +1489,25 @@ message BluetoothLinkLayerConnectionEvent { optional android.bluetooth.hci.StatusEnum reason_code = 9; } +/** + * Logs when a module is rolled back by Watchdog. + * + * Logged from: Rollback Manager + */ +message WatchdogRollbackOccurred { + enum RollbackType { + UNKNOWN = 0; + ROLLBACK_INITIATE = 1; + ROLLBACK_SUCCESS = 2; + ROLLBACK_FAILURE = 3; + } + optional RollbackType rollback_type = 1; + + optional string package_name = 2; + + optional int32 package_version_code = 3; +} + /** * Logs when something is plugged into or removed from the USB-C connector. @@ -2346,58 +2376,95 @@ message GenericAtom { } /** - * Logs when a fingerprint acquire event occurs. + * Logs when a biometric acquire event occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java + * frameworks/base/services/core/java/com/android/server/biometrics */ -message FingerprintAcquired { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this acquire is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; +message BiometricAcquired { + // Biometric modality that was acquired. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java. + optional int32 user = 2; + // If this acquire is for a crypto operation. e.g. Secure purchases, unlock password storage. + optional bool is_crypto = 3; + // Action that the device is performing. Acquired messages are only expected for enroll and + // authenticate. Other actions may indicate an error. + optional android.hardware.biometrics.ActionEnum action = 4; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 5; + // Acquired constants, e.g. ACQUIRED_GOOD. See constants defined by <Biometric>Manager. + optional int32 acquire_info = 6; + // Vendor-specific acquire info. Valid only if acquire_info == ACQUIRED_VENDOR. + optional int32 acquire_info_vendor = 7; } /** - * Logs when a fingerprint authentication event occurs. + * Logs when a biometric authentication event occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java - */ -message FingerprintAuthenticated { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this authentication is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; - // Whether or not this authentication was successful. - optional bool is_authenticated = 3; + * frameworks/base/services/core/java/com/android/server/biometrics + */ +message BiometricAuthenticated { + // Biometric modality that was used. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java + optional int32 user = 2; + // If this authentication is for a crypto operation. e.g. Secure purchases, unlock password + // storage. + optional bool is_crypto = 3; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 4; + + enum State { + UNKNOWN = 0; + REJECTED = 1; + PENDING_CONFIRMATION = 2; + CONFIRMED = 3; + } + + // State of the current auth attempt. + optional State state = 5; + // Time it took to authenticate. For BiometricPrompt where setRequireConfirmation(false) is + // specified and supported by the biometric modality, this is from the first ACQUIRED_GOOD to + // AUTHENTICATED. for setRequireConfirmation(true), this is from PENDING_CONFIRMATION to + // CONFIRMED. + optional int32 latency = 6; } /** - * Logs when a fingerprint error occurs. + * Logs when a biometric error occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java + * frameworks/base/services/core/java/com/android/server/biometrics + */ +message BiometricErrorOccurred { + // Biometric modality that was used. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java + optional int32 user = 2; + // If this error is for a crypto operation. e.g. Secure purchases, unlock password storage. + optional bool is_crypto = 3; + // Action that the device is performing. + optional android.hardware.biometrics.ActionEnum action = 4; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 5; + // Error constants. See constants defined by <Biometric>Manager. Enums won't work since errors + // are unique to modality. + optional int32 error_info = 6; + // Vendor-specific error info. Valid only if acquire_info == ACQUIRED_VENDOR. These are defined + // by the vendor and not specified by the HIDL interface. + optional int32 error_info_vendor = 7; +} + +/** + * Logs when a biometric HAL has crashed. + * Logged from: + * frameworks/base/services/core/java/com/android/server/biometrics */ -message FingerprintErrorOccurred { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this error is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; - - enum Error { - UNKNOWN = 0; - LOCKOUT = 1; - PERMANENT_LOCKOUT = 2; - } - // The type of error. - optional Error error = 3; +message BiometricHalDeathReported { + // Biometric modality. + optional android.hardware.biometrics.ModalityEnum modality = 1; } message Notification { @@ -2471,40 +2538,44 @@ message Notification { } /* - * Logs when a flag flip state changes. - * Logged in P/h. + * Logs when a flag flip update occurrs. Used for mainline modules that update via flag flips. */ -message PhenotypeFlagStateChanged { - // Mendel configuration name. - optional string mendel_config_name = 1; - // State - enum State { - STATE_UNKNOWN = 0; - COMMITTED = 1; - } - optional State state = 2; +message FlagFlipUpdateOccurred { + // If the event is from a flag config package, specify the package name. + optional string flag_flip_package_name = 1; + + // The order id of the package + optional int64 order_id = 2; } /* * Logs when a binary push state changes. - * Logged in Play store + * Logged by the installer via public api. */ message BinaryPushStateChanged { - // Binary package name. - optional string binary_name = 1; - // Version code. - optional int64 version = 2; - // State + // Name of the train. + optional string train_name = 1; + // Version code for a "train" of packages that need to be installed atomically + optional int64 train_version_code = 2; + // After installation of this package, device requires a restart. + optional bool requires_staging = 3; + // Rollback should be enabled for this install. + optional bool rollback_enabled = 4; + // Requires low latency monitoring if possible. + optional bool requires_low_latency_monitor = 5; + enum State { - STATE_UNKNOWN = 0; - DOWNLOAD_START = 1; - DOWNLOAD_DONE = 2; - INSTALL_START = 3; - INSTALL_DONE = 4; - REBOOT_START = 5; - REBOOT_DONE = 6; + UNKNOWN = 0; + INSTALL_REQUESTED = 1; + INSTALL_STARTED = 2; + INSTALL_STAGED_NOT_READY = 3; + INSTALL_STAGED_READY = 4; + INSTALL_SUCCESS = 5; + INSTALL_FAILURE = 6; + INSTALL_CANCELLED = 7; + INSTALLER_ROLLBACK_REQUESTED = 8; } - optional State state = 3; + optional State state = 6; } /** Represents USB port overheat event. */ @@ -3016,8 +3087,8 @@ message DiskSpace { /** * Pulls battery coulomb counter, which is the remaining battery charge in uAh. - * Pulled from: - * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp + * + * Pulled from StatsCompanionService.java */ message RemainingBatteryCapacity { optional int32 charge_micro_ampere_hour = 1; @@ -3336,14 +3407,14 @@ message DiskIo { /** * Pulls the number of fingerprints for each user. * - * Pulled from StatsCompanionService, which queries FingerprintManager. + * Pulled from StatsCompanionService, which queries <Biometric>Manager. */ -message NumFingerprints { +message NumBiometricsEnrolled { // The associated user. Eg: 0 for owners, 10+ for others. // Defined in android/os/UserHandle.java optional int32 user = 1; // Number of fingerprints registered to that user. - optional int32 num_fingerprints = 2; + optional int32 num_enrolled = 2; } message AggStats { @@ -4466,7 +4537,7 @@ message SeOmapiReported { // SIMx or eSEx. optional string terminal = 2; - optional string packageName = 3; + optional string package_name = 3; } /** @@ -4497,3 +4568,86 @@ message AttentionManagerServiceResultReported { } optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN]; } + +/** + * Logs when an adb connection changes state. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/adb/AdbDebuggingManager.java + */ +message AdbConnectionChanged { + // The last time this system connected via adb, or 0 if the 'always allow' option was not + // previously selected for this system. + optional int64 last_connection_time_millis = 1; + + // The time in ms within which a subsequent connection from an 'always allow' system is allowed + // to reconnect via adb without user interaction. + optional int64 auth_window_millis = 2; + + // The state of the adb connection from frameworks/base/core/proto/android/debug/enums.proto. + optional android.debug.AdbConnectionStateEnum state = 3; + + // True if the 'always allow' option was selected for this system. + optional bool always_allow = 4; +} + +/* + * Logs the reported speech DSP status. + * + * Logged from: + * Vendor audio implementation. + */ +message SpeechDspStatReported { + // The total Speech DSP uptime in milliseconds. + optional int32 total_uptime_millis = 1; + // The total Speech DSP downtime in milliseconds. + optional int32 total_downtime_millis = 2; + optional int32 total_crash_count = 3; + optional int32 total_recover_count = 4; +} + +/** + * Logs USB connector contaminant status. + * + * Logged from: USB Service. + */ +message UsbContaminantReported { + optional string id = 1; + optional android.service.usb.ContaminantPresenceStatus status = 2; +} + +/** + * This atom is for debugging purpose. + */ +message DebugElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; + + enum Type { + TYPE_UNKNOWN = 0; + ALWAYS_PRESENT = 1; + PRESENT_ON_ODD_PULLS = 2; + } + // Type of behavior for the pulled data. + optional Type type = 5; +} + +/** + * This atom is for debugging purpose. + */ +message DebugFailingElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; +} diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 5fea90b61c80..aa22333ab26c 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -128,6 +128,17 @@ void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { mConfigReceivers.erase(key); } +void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, + const sp<IBinder>& intentSender) { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers[uid] = intentSender; +} + +void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers.erase(uid); +} + void ConfigManager::RemoveConfig(const ConfigKey& key) { vector<sp<ConfigListener>> broadcastList; { @@ -181,6 +192,11 @@ void ConfigManager::RemoveConfigs(int uid) { mConfigReceivers.erase(*it); } + auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); + if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { + mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); + } + mConfigs.erase(uidIt); for (const sp<ConfigListener>& listener : mListeners) { @@ -213,6 +229,7 @@ void ConfigManager::RemoveAllConfigs() { } mConfigReceivers.clear(); + mActiveConfigsChangedReceivers.clear(); for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } @@ -250,6 +267,17 @@ const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key } } +const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const { + lock_guard<mutex> lock(mMutex); + + auto it = mActiveConfigsChangedReceivers.find(uid); + if (it == mActiveConfigsChangedReceivers.end()) { + return nullptr; + } else { + return it->second; + } +} + void ConfigManager::Dump(FILE* out) { lock_guard<mutex> lock(mMutex); diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h index 122e669057b0..c064a519f597 100644 --- a/cmds/statsd/src/config/ConfigManager.h +++ b/cmds/statsd/src/config/ConfigManager.h @@ -82,6 +82,23 @@ public: void RemoveConfigReceiver(const ConfigKey& key); /** + * Sets the broadcast receiver that is notified whenever the list of active configs + * changes for this uid. + */ + void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender); + + /** + * Returns the broadcast receiver for active configs changed for this uid. + */ + + const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const; + + /** + * Erase any active configs changed broadcast receiver associated with this uid. + */ + void RemoveActiveConfigsChangedReceiver(const int uid); + + /** * A configuration was removed. * * Reports this to listeners. @@ -130,6 +147,12 @@ private: std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers; /** + * Each uid can be subscribed by up to one receiver to notify that the list of active configs + * for this uid has changed. The receiver is specified as IBinder from PendingIntent. + */ + std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers; + + /** * The ConfigListeners that will be told about changes. */ std::vector<sp<ConfigListener>> mListeners; diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp b/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp deleted file mode 100644 index 53709f14564d..000000000000 --- a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include <android/hardware/thermal/2.0/IThermal.h> -#include "external/ResourceThermalManagerPuller.h" -#include "external/StatsPuller.h" - -#include "ResourceThermalManagerPuller.h" -#include "logd/LogEvent.h" -#include "statslog.h" -#include "stats_log_util.h" - -#include <chrono> - -using android::hardware::hidl_death_recipient; -using android::hardware::hidl_vec; -using android::hidl::base::V1_0::IBase; -using ::android::hardware::thermal::V2_0::IThermal; -using ::android::hardware::thermal::V2_0::Temperature; -using ::android::hardware::thermal::V2_0::TemperatureType; -using ::android::hardware::thermal::V1_0::ThermalStatus; -using ::android::hardware::thermal::V1_0::ThermalStatusCode; -using android::hardware::Return; -using android::hardware::Void; - -using std::chrono::duration_cast; -using std::chrono::nanoseconds; -using std::chrono::system_clock; -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -bool getThermalHalLocked(); -sp<android::hardware::thermal::V2_0::IThermal> gThermalHal = nullptr; -std::mutex gThermalHalMutex; - -struct ThermalHalDeathRecipient : virtual public hidl_death_recipient { - virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override { - std::lock_guard<std::mutex> lock(gThermalHalMutex); - ALOGE("ThermalHAL just died"); - gThermalHal = nullptr; - getThermalHalLocked(); - } -}; - -sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr; - -// The caller must be holding gThermalHalMutex. -bool getThermalHalLocked() { - if (gThermalHal == nullptr) { - gThermalHal = IThermal::getService(); - if (gThermalHal == nullptr) { - ALOGE("Unable to get Thermal service."); - } else { - if (gThermalHalDeathRecipient == nullptr) { - gThermalHalDeathRecipient = new ThermalHalDeathRecipient(); - } - hardware::Return<bool> linked = gThermalHal->linkToDeath( - gThermalHalDeathRecipient, 0x451F /* cookie */); - if (!linked.isOk()) { - ALOGE("Transaction error in linking to ThermalHAL death: %s", - linked.description().c_str()); - gThermalHal = nullptr; - } else if (!linked) { - ALOGW("Unable to link to ThermalHal death notifications"); - gThermalHal = nullptr; - } else { - ALOGD("Link to death notification successful"); - } - } - } - return gThermalHal != nullptr; -} - -ResourceThermalManagerPuller::ResourceThermalManagerPuller() : - StatsPuller(android::util::TEMPERATURE) { -} - -bool ResourceThermalManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - std::lock_guard<std::mutex> lock(gThermalHalMutex); - if (!getThermalHalLocked()) { - ALOGE("Thermal Hal not loaded"); - return false; - } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - - data->clear(); - bool resultSuccess = true; - - Return<void> ret = gThermalHal->getCurrentTemperatures(false, TemperatureType::SKIN, - [&](ThermalStatus status, const hidl_vec<Temperature>& temps) { - if (status.code != ThermalStatusCode::SUCCESS) { - ALOGE("Failed to get temperatures from ThermalHAL. Status: %d", status.code); - resultSuccess = false; - return; - } - if (mTagId == android::util::TEMPERATURE) { - for (size_t i = 0; i < temps.size(); ++i) { - auto ptr = make_shared<LogEvent>(android::util::TEMPERATURE, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write((static_cast<int>(temps[i].type))); - ptr->write(temps[i].name); - // Convert the temperature to an int. - int32_t temp = static_cast<int>(temps[i].value * 10); - ptr->write(temp); - ptr->init(); - data->push_back(ptr); - } - } else { - ALOGE("Unsupported tag in ResourceThermalManagerPuller: %d", mTagId); - } - }); - if (!ret.isOk()) { - ALOGE("getThermalHalLocked() failed: thermal HAL service not available. Description: %s", - ret.description().c_str()); - if (ret.isDeadObject()) { - gThermalHal = nullptr; - } - return false; - } - return resultSuccess; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 19a7389cb3d9..ba7bcc437d74 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -28,7 +28,6 @@ #include "../statscompanion_util.h" #include "PowerStatsPuller.h" #include "ResourceHealthManagerPuller.h" -#include "ResourceThermalManagerPuller.h" #include "StatsCompanionServicePuller.h" #include "StatsPullerManager.h" #include "SubsystemSleepStatePuller.h" @@ -150,7 +149,8 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, // temperature - {android::util::TEMPERATURE, {.puller = new ResourceThermalManagerPuller()}}, + {android::util::TEMPERATURE, + {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, // binder_calls {android::util::BINDER_CALLS, {.additiveFields = {4, 5, 6, 8, 12}, @@ -175,8 +175,8 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {android::util::CATEGORY_SIZE, {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, // Number of fingerprints registered to each user. - {android::util::NUM_FINGERPRINTS, - {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}}, + {android::util::NUM_FINGERPRINTS_ENROLLED, + {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}}, // ProcStats. {android::util::PROC_STATS, {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}}, @@ -209,6 +209,14 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER, {.puller = new StatsCompanionServicePuller( android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, + // DebugElapsedClock. + {android::util::DEBUG_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}}, + // DebugFailingElapsedClock. + {android::util::DEBUG_FAILING_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, // BuildInformation. {android::util::BUILD_INFORMATION, {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 78a75c5ffaf0..eaba9bee6bf0 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -267,6 +267,22 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, con } LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const SpeechDspStat& speechDspStat) { + mLogdTimestampNs = wallClockTimestampNs; + mElapsedTimestampNs = elapsedTimestampNs; + mTagId = android::util::SPEECH_DSP_STAT_REPORTED; + + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), + Value(speechDspStat.totalUptimeMillis))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), + Value(speechDspStat.totalDowntimeMillis))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), + Value(speechDspStat.totalCrashCount))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), + Value(speechDspStat.totalRecoverCount))); +} + +LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const BatteryCausedShutdown& batteryCausedShutdown) { mLogdTimestampNs = wallClockTimestampNs; mElapsedTimestampNs = elapsedTimestampNs; @@ -294,6 +310,36 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, Value(usbPortOverheatEvent.timeToInactive))); } +LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const VendorAtom& vendorAtom) { + mLogdTimestampNs = wallClockTimestampNs; + mElapsedTimestampNs = elapsedTimestampNs; + mTagId = vendorAtom.atomId; + + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName))); + for (int i = 0; i < (int)vendorAtom.values.size(); i++) { + switch (vendorAtom.values[i].getDiscriminator()) { + case VendorAtom::Value::hidl_discriminator::intValue: + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), + Value(vendorAtom.values[i].intValue()))); + break; + case VendorAtom::Value::hidl_discriminator::longValue: + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), + Value(vendorAtom.values[i].longValue()))); + break; + case VendorAtom::Value::hidl_discriminator::floatValue: + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), + Value(vendorAtom.values[i].floatValue()))); + break; + case VendorAtom::Value::hidl_discriminator::stringValue: + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), + Value(vendorAtom.values[i].stringValue()))); + break; + } + } +} + LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {} LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 3f47b7efdfb5..784376a1580c 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -121,6 +121,12 @@ public: explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const UsbPortOverheatEvent& usbPortOverheatEvent); + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const SpeechDspStat& speechDspStat); + + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const VendorAtom& vendorAtom); + ~LogEvent(); /** diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index e9c43cdbf31f..5cf012638dce 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -548,6 +548,7 @@ const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_LMKD", 1069}, {"AID_LLKD", 1070}, {"AID_IORAPD", 1071}, + {"AID_NETWORK_STACK", 1073}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index de818a8fd77d..d9aba61e31c9 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -55,7 +55,8 @@ public class TestDrive { "AID_ROOT", "AID_BLUETOOTH", "AID_LMKD", - "com.android.managedprovisioning" + "com.android.managedprovisioning", + "AID_NETWORK_STACK" }; private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index fc47f67f174c..8e7a58b40d0c 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -901,7 +901,6 @@ Landroid/os/ParcelableParcel;->getClassLoader()Ljava/lang/ClassLoader; Landroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel; Landroid/os/ParcelFileDescriptor;-><init>(Ljava/io/FileDescriptor;)V Landroid/os/ParcelFileDescriptor;->fromData([BLjava/lang/String;)Landroid/os/ParcelFileDescriptor; -Landroid/os/ParcelFileDescriptor;->getFile(Ljava/io/FileDescriptor;)Ljava/io/File; Landroid/os/ParcelFileDescriptor;->seekTo(J)J Landroid/os/PerformanceCollector;-><init>()V Landroid/os/PerformanceCollector;->beginSnapshot(Ljava/lang/String;)V @@ -3518,7 +3517,7 @@ Lcom/android/internal/telephony/SubscriptionController;->mDefaultPhoneId:I Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object; Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V -Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V +Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 1063be4c5c7d..92f47e767d2e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -8218,10 +8218,10 @@ public class Activity extends ContextThemeWrapper final AutofillId autofillId = autofillIds[i]; final View view = autofillClientFindViewByAutofillIdTraversal(autofillId); if (view != null) { - if (!autofillId.isVirtual()) { + if (!autofillId.isVirtualInt()) { visible[i] = view.isVisibleToUser(); } else { - visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId()); + visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId()); } } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e0b8d78ebabc..1045b7aa0890 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3529,12 +3529,32 @@ public class ActivityManager { /** * Returns "true" if device is running in a test harness. + * + * @deprecated this method is false for all user builds. Users looking to check if their device + * is running in a device farm should see {@link #isRunningInUserTestHarness()}. */ + @Deprecated public static boolean isRunningInTestHarness() { return SystemProperties.getBoolean("ro.test_harness", false); } /** + * Returns "true" if the device is running in Test Harness Mode. + * + * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a + * device farm/testing harness (such as Firebase Test Lab). You should check this method if you + * want your app to behave differently when running in a test harness to skip setup screens that + * would impede UI testing. e.g. a keyboard application that has a full screen setup page for + * the first time it is launched. + * + * <p>Note that you should <em>not</em> use this to determine whether or not your app is running + * an instrumentation test, as it is not set for a standard device running a test. + */ + public static boolean isRunningInUserTestHarness() { + return SystemProperties.getBoolean("persist.sys.test_harness", false); + } + + /** * Unsupported compiled sdk warning should always be shown for the intput activity * even in cases where the system would normally not show the warning. E.g. when running in a * test harness. diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 5cac0489e8df..86e658dfdcb1 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -219,9 +219,11 @@ public abstract class ActivityManagerInternal { * @param userId * @param event * @param appToken ActivityRecord's appToken. + * @param taskRoot TaskRecord's root */ public abstract void updateActivityUsageStats( - ComponentName activity, int userId, int event, IBinder appToken); + ComponentName activity, int userId, int event, IBinder appToken, + ComponentName taskRoot); public abstract void updateForegroundTimeIfOnBattery( String packageName, int uid, long cpuTimeDiff); public abstract void sendForegroundProfileChanged(int userId); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a3243a5de72a..ee3d27c7dac8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -77,9 +77,7 @@ import android.graphics.ImageDecoder; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.Network; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -1033,15 +1031,10 @@ public final class ActivityThread extends ClientTransactionHandler { NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); } - public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) { + public void updateHttpProxy() { final ConnectivityManager cm = ConnectivityManager.from( getApplication() != null ? getApplication() : getSystemContext()); - final Network network = cm.getBoundNetworkForProcess(); - if (network != null) { - Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); - } else { - Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); - } + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); } public void processInBackground() { @@ -5960,8 +5953,7 @@ public final class ActivityThread extends ClientTransactionHandler { // crash if we can't get it. final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - final ProxyInfo proxyInfo = service.getProxyForNetwork(null); - Proxy.setHttpProxySystemProperty(proxyInfo); + Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); } catch (RemoteException e) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 4d3711ae7ff5..47398674d74c 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -23,6 +23,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager.StackInfo; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -119,6 +120,7 @@ public class ActivityView extends ViewGroup { /** Callback that notifies when the container is ready or destroyed. */ public abstract static class StateCallback { + /** * Called when the container is ready for launching activities. Calling * {@link #startActivity(Intent)} prior to this callback will result in an @@ -127,6 +129,7 @@ public class ActivityView extends ViewGroup { * @see #startActivity(Intent) */ public abstract void onActivityViewReady(ActivityView view); + /** * Called when the container can no longer launch activities. Calling * {@link #startActivity(Intent)} after this callback will result in an @@ -135,11 +138,24 @@ public class ActivityView extends ViewGroup { * @see #startActivity(Intent) */ public abstract void onActivityViewDestroyed(ActivityView view); + + /** + * Called when a task is created inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskCreated(int taskId, ComponentName componentName) { } + /** * Called when a task is moved to the front of the stack inside the container. * This is a filtered version of {@link TaskStackListener} */ public void onTaskMovedToFront(ActivityManager.StackInfo stackInfo) { } + + /** + * Called when a task is about to be removed from the stack inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskRemovalStarted(int taskId) { } } /** @@ -508,14 +524,45 @@ public class ActivityView extends ViewGroup { @Override public void onTaskMovedToFront(int taskId) throws RemoteException { - if (mActivityViewCallback != null) { - StackInfo stackInfo = getTopMostStackInfo(); - // if StackInfo was null or unrelated to the "move to front" then there's no use - // notifying the callback - if (stackInfo != null - && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { - mActivityViewCallback.onTaskMovedToFront(stackInfo); - } + if (mActivityViewCallback == null) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the "move to front" then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskMovedToFront(stackInfo); + } + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { + if (mActivityViewCallback == null) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the task creation then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskCreated(taskId, componentName); + } + } + + @Override + public void onTaskRemovalStarted(int taskId) throws RemoteException { + if (mActivityViewCallback == null) { + return; + } + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or task is on a different display then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskRemovalStarted(taskId); } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 5868771ff5c4..364d3c9c8e5d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -27,6 +27,7 @@ import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.usage.UsageStatsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.database.ContentObserver; import android.media.AudioAttributes.AttributeUsage; @@ -38,12 +39,13 @@ import android.os.Parcelable; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import android.util.SparseArray; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsNotedCallback; @@ -60,8 +62,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** @@ -1489,7 +1491,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS - AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW + getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS AppOpsManager.MODE_ALLOWED, // CAMERA AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO @@ -1711,6 +1713,12 @@ public class AppOpsManager { /** @hide */ public static final String KEY_HISTORICAL_OPS = "historical_ops"; + /** System properties for debug logging of noteOp call sites */ + private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled"; + private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages"; + private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops"; + private static final String DEBUG_LOGGING_TAG = "AppOpsManager"; + /** * Retrieve the op switch that controls the given operation. * @hide @@ -4468,6 +4476,7 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int noteOpNoThrow(int op, int uid, String packageName) { + logNoteOpIfNeeded(op, packageName); try { return mService.noteOperation(op, uid, packageName); } catch (RemoteException e) { @@ -4816,4 +4825,62 @@ public class AppOpsManager { } } } + + private static int getSystemAlertWindowDefault() { + final Context context = ActivityThread.currentApplication(); + if (context == null) { + return AppOpsManager.MODE_DEFAULT; + } + + // system alert window is disable on low ram phones starting from Q + final PackageManager pm = context.getPackageManager(); + // TVs are constantly plugged in and has less concern for memory/power + if (ActivityManager.isLowRamDeviceStatic() + && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) { + return AppOpsManager.MODE_IGNORED; + } + + return AppOpsManager.MODE_DEFAULT; + } + + private static void logNoteOpIfNeeded(int op, String callingPackage) { + // Check if debug logging propety is enabled. + if (!SystemProperties.getBoolean(DEBUG_LOGGING_ENABLE_PROP, false)) { + return; + } + // Check if this package should be logged. + String packages = SystemProperties.get(DEBUG_LOGGING_PACKAGES_PROP, ""); + if (!"".equals(packages) && callingPackage != null) { + boolean found = false; + for (String pkg : packages.split(",")) { + if (callingPackage.equals(pkg)) { + found = true; + break; + } + } + if (!found) { + return; + } + } + String opStr = opToName(op); + // Check if this app op should be logged + String logOps = SystemProperties.get(DEBUG_LOGGING_OPS_PROP, ""); + if (!"".equals(logOps)) { + boolean found = false; + for (String logOp : logOps.split(",")) { + if (opStr.equals(logOp)) { + found = true; + break; + } + } + if (!found) { + return; + } + } + + // Log a stack trace + Exception here = new Exception("HERE!"); + android.util.Log.i(DEBUG_LOGGING_TAG, "Note operation package= " + callingPackage + + " op= " + opStr, here); + } } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index cc6c999ed255..db6ad3df17b0 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -434,6 +434,11 @@ interface IActivityTaskManager { void registerRemoteAnimationForNextActivityStart(in String packageName, in RemoteAnimationAdapter adapter); + /** + * Registers remote animations for a display. + */ + void registerRemoteAnimationsForDisplay(int displayId, in RemoteAnimationDefinition definition); + /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index fcb6c14d052c..c64fcf3e7526 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -100,8 +100,7 @@ oneway interface IApplicationThread { void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); void clearDnsCache(); - void setHttpProxy(in String proxy, in String port, in String exclList, - in Uri pacFileUrl); + void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info); void scheduleTrimMemory(int level); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 1ae0f52ac74e..f522d71afd08 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -19,6 +19,7 @@ package android.app; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -28,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -86,6 +88,12 @@ public class KeyguardManager { "android.app.action.CONFIRM_FRP_CREDENTIAL"; /** + * @hide + */ + public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE = + "android.app.extra.BIOMETRIC_PROMPT_BUNDLE"; + + /** * A CharSequence dialog title to show to the user when used with a * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. * @hide @@ -117,14 +125,19 @@ public class KeyguardManager { public static final int RESULT_ALTERNATE = 1; /** - * Get an intent to prompt the user to confirm credentials (pin, pattern or password) - * for the current user of the device. The caller is expected to launch this activity using - * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for + * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)} + * + * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics + * if enrolled) for the current user of the device. The caller is expected to launch this + * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. * * @return the intent for launching the activity or null if no password is required. **/ - public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { + @Deprecated + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) + public Intent createConfirmDeviceCredentialIntent(CharSequence title, + CharSequence description) { if (!isDeviceSecure()) return null; Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); intent.putExtra(EXTRA_TITLE, title); @@ -176,6 +189,7 @@ public class KeyguardManager { * @throws IllegalStateException if the device has already been provisioned * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @SystemApi public Intent createConfirmFactoryResetCredentialIntent( CharSequence title, CharSequence description, CharSequence alternateButtonLabel) { @@ -231,6 +245,7 @@ public class KeyguardManager { * secure notifications cannot be shown if {@code false} * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public void setPrivateNotificationsAllowed(boolean allow) { @@ -249,6 +264,7 @@ public class KeyguardManager { * By default, private notifications are allowed. * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public boolean getPrivateNotificationsAllowed() { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b8d748dce9e6..7c550d4332fe 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -84,7 +84,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -5257,7 +5256,11 @@ public class Notification implements Parcelable * @hide */ public RemoteViews makeAmbientNotification() { - return createHeadsUpContentView(false /* increasedHeight */); + RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); + if (headsUpContentView != null) { + return headsUpContentView; + } + return createContentView(); } private void hideLine1Text(RemoteViews result) { @@ -8399,6 +8402,30 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * in its expanded state, with the contents of {@link #getIntent()} in a floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * <b>without</b> the associated notification in the notification shade. Subsequent update + * notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002; private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) { mPendingIntent = intent; @@ -8412,6 +8439,7 @@ public class Notification implements Parcelable mTitle = in.readCharSequence(); mIcon = Icon.CREATOR.createFromParcel(in); mDesiredHeight = in.readInt(); + mFlags = in.readInt(); } /** @@ -8444,6 +8472,24 @@ public class Notification implements Parcelable return mDesiredHeight; } + /** + * @return whether this bubble should auto expand when it is posted. + * + * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) + */ + public boolean getAutoExpandBubble() { + return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; + } + + /** + * @return whether this bubble should suppress the initial notification when it is posted. + * + * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean) + */ + public boolean getSuppressInitialNotification() { + return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0; + } + public static final Parcelable.Creator<BubbleMetadata> CREATOR = new Parcelable.Creator<BubbleMetadata>() { @@ -8469,6 +8515,11 @@ public class Notification implements Parcelable out.writeCharSequence(mTitle); mIcon.writeToParcel(out, 0); out.writeInt(mDesiredHeight); + out.writeInt(mFlags); + } + + private void setFlags(int flags) { + mFlags = flags; } /** @@ -8480,6 +8531,7 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; /** * Constructs a new builder object. @@ -8539,6 +8591,39 @@ public class Notification implements Parcelable } /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted in its expanded state, with the contents of {@link #getIntent()} in a + * floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { + setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); + return this; + } + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted <b>without</b> the associated notification in the notification shade. + * Subsequent update notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setSuppressInitialNotification( + boolean shouldSupressNotif) { + setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif); + return this; + } + + /** * Creates the {@link BubbleMetadata} defined by this builder. * <p>Will throw {@link IllegalStateException} if required fields have not been set * on this builder.</p> @@ -8553,7 +8638,22 @@ public class Notification implements Parcelable if (mIcon == null) { throw new IllegalStateException("Must supply an icon for the bubble"); } - return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight); + BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon, + mDesiredHeight); + data.setFlags(mFlags); + return data; + } + + /** + * @hide + */ + public BubbleMetadata.Builder setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + return this; } } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 43614feb28a4..c4b4b4070ce7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1137,9 +1137,6 @@ public class NotificationManager { } } - /** - * @hide - */ public boolean isNotificationAssistantAccessGranted(ComponentName assistant) { INotificationManager service = getService(); try { diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 9dcd1228522b..9b66c928abc0 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -73,6 +73,11 @@ public final class StatsManager { */ public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE"; + /** + * Long array extra of the active configs for the uid that added those configs. + */ + public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = + "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; /** * Broadcast Action: Statsd has started. @@ -274,6 +279,43 @@ public final class StatsManager { } } + /** + * Registers the operation that is called whenever there is a change in which configs are + * active. This must be called each time statsd starts. This operation allows + * statsd to inform clients that they should pull data of the configs that are currently + * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs + * that are active and stop pulling data of configs that are no longer active. + * + * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber + * associated with the given subscriberId. May be null, in which case + * it removes any associated pending intent for this client. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service + */ + @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) + public void setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent) + throws StatsUnavailableException { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (pendingIntent == null) { + service.removeActiveConfigsChangedOperation(mContext.getOpPackageName()); + } else { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + service.setActiveConfigsChangedOperation(intentSender, + mContext.getOpPackageName()); + } + + } catch (RemoteException e) { + Slog.e(TAG, + "Failed to connect to statsd when registering active configs listener."); + throw new StatsUnavailableException("could not connect", e); + } catch (SecurityException e) { + throw new StatsUnavailableException(e.getMessage(), e); + } + } + } + // TODO: Temporary for backwards compatibility. Remove. /** * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 86db99bf0b29..2514eee09da0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1362,16 +1362,23 @@ public class DevicePolicyManager { public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; /** - * Activity action: have the user enter a new password. This activity should - * be launched after using {@link #setPasswordQuality(ComponentName, int)}, - * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user - * enter a new password that meets the current requirements. You can use - * {@link #isActivePasswordSufficient()} to determine whether you need to - * have the user select a new password in order to meet the current - * constraints. Upon being resumed from this activity, you can check the new + * Activity action: have the user enter a new password. + * + * <p>For admin apps, this activity should be launched after using {@link + * #setPasswordQuality(ComponentName, int)}, or {@link + * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that + * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to + * determine whether you need to have the user select a new password in order to meet the + * current constraints. Upon being resumed from this activity, you can check the new * password characteristics to see if they are sufficient. * - * If the intent is launched from within a managed profile with a profile + * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock + * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest + * to users how complex the app wants the new screen lock to be. Note that both {@link + * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the + * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}. + * + * <p>If the intent is launched from within a managed profile with a profile * owner built against {@link android.os.Build.VERSION_CODES#M} or before, * this will trigger entering a new password for the parent of the profile. * For all other cases it will trigger entering a new password for the user @@ -1384,6 +1391,24 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** + * An integer indicating the complexity level of the new password an app would like the user to + * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}. + * + * <p>Must be one of + * <ul> + * <li>{@link #PASSWORD_COMPLEXITY_HIGH} + * <li>{@link #PASSWORD_COMPLEXITY_MEDIUM} + * <li>{@link #PASSWORD_COMPLEXITY_LOW} + * <li>{@link #PASSWORD_COMPLEXITY_NONE} + * </ul> + * + * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}. + */ + @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) + public static final String EXTRA_PASSWORD_COMPLEXITY = + "android.app.extra.PASSWORD_COMPLEXITY"; + + /** * Constant for {@link #getPasswordComplexity()}: no password. * * <p>Note that these complexity constants are ordered so that higher values are more complex. @@ -2513,6 +2538,9 @@ public class DevicePolicyManager { * requested quality constant (between the policy set here, the user's preference, and any other * considerations) is the one that is in effect. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2548,6 +2576,9 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * + * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, + * the password is always treated as empty. + * * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2580,6 +2611,9 @@ public class DevicePolicyManager { * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2609,11 +2643,13 @@ public class DevicePolicyManager { * restrictions on this user and its participating profiles. Restrictions on profiles that have * a separate challenge are not taken into account. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * - * user and its profiles or a particular one. * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2644,6 +2680,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2678,6 +2717,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2714,6 +2756,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2748,6 +2793,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2784,6 +2832,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2818,6 +2869,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2853,6 +2907,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2887,6 +2944,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2922,6 +2982,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2955,6 +3018,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2990,6 +3056,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3024,6 +3093,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -3060,6 +3132,9 @@ public class DevicePolicyManager { * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3112,6 +3187,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or {@code admin} * does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) { if (mService != null) { try { @@ -3136,6 +3212,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The timeout for the given admin or the minimum of all timeouts */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpirationTimeout(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3160,6 +3237,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The password expiration time, in milliseconds since epoch. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpiration(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3184,12 +3262,14 @@ public class DevicePolicyManager { * all admins. * @return The length of the password history */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin) { return getPasswordHistoryLength(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3204,10 +3284,16 @@ public class DevicePolicyManager { /** * Return the maximum password length that the device supports for a * particular password quality. + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always empty. * @param quality The quality being interrogated. * @return Returns the maximum length that the user can enter. */ public int getPasswordMaximumLength(int quality) { + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) { + return 0; + } // Kind-of arbitrary. return 16; } @@ -3218,6 +3304,10 @@ public class DevicePolicyManager { * user and its participating profiles. Restrictions on profiles that have a separate challenge * are not taken into account. The user must be unlocked in order to perform the check. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty - i.e. this method will always return false on such + * devices, provided any password requirements were set. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3250,6 +3340,9 @@ public class DevicePolicyManager { * explicitly querying the parent profile screen lock complexity via {@link * #getParentProfileInstance}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * @throws IllegalStateException if the user is not unlocked. * @throws SecurityException if the calling application does not have the permission * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} @@ -3329,6 +3422,7 @@ public class DevicePolicyManager { * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getCurrentFailedPasswordAttempts() { return getCurrentFailedPasswordAttempts(myUserId()); } @@ -3396,6 +3490,7 @@ public class DevicePolicyManager { * both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) { if (mService != null) { try { @@ -3419,12 +3514,14 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) { return getMaximumFailedPasswordsForWipe(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3444,6 +3541,7 @@ public class DevicePolicyManager { * user passed in. * @hide Used only by Keyguard */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) { if (mService != null) { try { @@ -3514,6 +3612,7 @@ public class DevicePolicyManager { * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} * @throws IllegalStateException if the calling user is locked or has a managed profile. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPassword(String password, int flags) { throwIfParentInstance("resetPassword"); if (mService != null) { @@ -3557,6 +3656,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalArgumentException if the supplied token is invalid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean setResetPasswordToken(ComponentName admin, byte[] token) { throwIfParentInstance("setResetPasswordToken"); if (mService != null) { @@ -3576,6 +3676,7 @@ public class DevicePolicyManager { * @return true if the operation is successful, false otherwise. * @throws SecurityException if admin is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean clearResetPasswordToken(ComponentName admin) { throwIfParentInstance("clearResetPasswordToken"); if (mService != null) { @@ -3596,6 +3697,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if no token has been set. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean isResetPasswordTokenActive(ComponentName admin) { throwIfParentInstance("isResetPasswordTokenActive"); if (mService != null) { @@ -3637,6 +3739,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if the provided token is not valid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password, byte[] token, int flags) { throwIfParentInstance("resetPassword"); @@ -3742,6 +3845,7 @@ public class DevicePolicyManager { * * @throws SecurityException if {@code admin} is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin, long timeoutMs) { if (mService != null) { @@ -3766,12 +3870,14 @@ public class DevicePolicyManager { * across all participating admins. * @return The timeout in milliseconds or 0 if not configured for the provided admin. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) { return getRequiredStrongAuthTimeout(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) { if (mService != null) { try { @@ -3874,6 +3980,10 @@ public class DevicePolicyManager { */ public static final int WIPE_EUICC = 0x0004; + /** + * Flag for {@link #wipeData(int)}: won't show reason for wiping to the user. + */ + public static final int WIPE_SILENTLY = 0x0008; /** * Ask that all user data be wiped. If called as a secondary user, the user will be removed and @@ -3885,9 +3995,10 @@ public class DevicePolicyManager { * be able to call this method; if it has not, a security exception will be thrown. * * @param flags Bit mask of additional options: currently supported flags are - * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}. + * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA}, + * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}. * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} + * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} */ public void wipeData(int flags) { throwIfParentInstance("wipeData"); @@ -3907,16 +4018,21 @@ public class DevicePolicyManager { * be able to call this method; if it has not, a security exception will be thrown. * * @param flags Bit mask of additional options: currently supported flags are - * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}. + * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and + * {@link #WIPE_EUICC}. * @param reason a string that contains the reason for wiping data, which can be - * presented to the user. If the string is null or empty, user won't be notified. + * presented to the user. * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} - * @throws IllegalArgumentException if the input reason string is null or empty. + * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} + * @throws IllegalArgumentException if the input reason string is null or empty, or if + * {@link #WIPE_SILENTLY} is set. */ - public void wipeData(int flags, CharSequence reason) { + public void wipeData(int flags, @NonNull CharSequence reason) { throwIfParentInstance("wipeData"); - wipeDataInternal(flags, reason != null ? reason.toString() : null); + Preconditions.checkNotNull(reason, "reason string is null"); + Preconditions.checkStringNotEmpty(reason, "reason string is empty"); + Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set"); + wipeDataInternal(flags, reason.toString()); } /** @@ -3927,7 +4043,7 @@ public class DevicePolicyManager { * @see #wipeData(int, CharSequence) * @hide */ - private void wipeDataInternal(int flags, String wipeReasonForUser) { + private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) { if (mService != null) { try { mService.wipeDataWithReason(flags, wipeReasonForUser); @@ -5350,6 +5466,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { if (mService != null) { try { @@ -5363,6 +5480,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportPasswordChanged(@UserIdInt int userId) { if (mService != null) { try { @@ -5377,6 +5495,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5391,6 +5510,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5404,6 +5524,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -5417,6 +5538,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -6383,6 +6505,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or does not use * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setTrustAgentConfiguration(@NonNull ComponentName admin, @NonNull ComponentName target, PersistableBundle configuration) { if (mService != null) { @@ -6412,6 +6535,7 @@ public class DevicePolicyManager { * @param agent Which component to get enabled features for. * @return configuration for the given trust agent. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent) { return getTrustAgentConfiguration(admin, agent, myUserId()); @@ -6419,6 +6543,7 @@ public class DevicePolicyManager { /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) { if (mService != null) { @@ -9403,18 +9528,12 @@ public class DevicePolicyManager { } /** - * Allows the device owner or profile owner to enable or disable the backup service. - * - * <p> Each user has its own backup service which manages the backup and restore mechanisms in - * that user. Disabling the backup service will prevent data from being backed up or restored. + * Allows the device owner to enable or disable the backup service. * - * <p> Device owner calls this API to control backup services across all users on the device. - * Profile owner can use this API to enable or disable the profile's backup service. However, - * for a managed profile its backup functionality is only enabled if both the device owner - * and the profile owner have enabled the backup service. + * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to + * false will prevent data from being backed up or restored. * - * <p> By default, backup service is disabled on a device with device owner, and within a - * managed profile. + * <p> Backup service is off by default when device owner is present. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. @@ -9430,12 +9549,7 @@ public class DevicePolicyManager { } /** - * Return whether the backup service is enabled by the device owner or profile owner for the - * current user, as previously set by {@link #setBackupServiceEnabled(ComponentName, boolean)}. - * - * <p> Whether the backup functionality is actually enabled or not depends on settings from both - * the current user and the device owner, please see - * {@link #setBackupServiceEnabled(ComponentName, boolean)} for details. + * Return whether the backup service is enabled by the device owner. * * <p> Backup service manages all backup and restore mechanisms on the device. * @@ -10324,76 +10438,53 @@ public class DevicePolicyManager { } /** - * Whitelists a package that is allowed to access cross profile calendar APIs. + * Whitelists a set of packages that are allowed to access cross-profile calendar APIs. * * <p>Called by a profile owner of a managed profile. * - * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName name of the package to be whitelisted. - * @throws SecurityException if {@code admin} is not a profile owner. - * - * @see #removeCrossProfileCalendarPackage(ComponentName, String) - * @see #getCrossProfileCalendarPackages(ComponentName) - */ - public void addCrossProfileCalendarPackage(@NonNull ComponentName admin, - @NonNull String packageName) { - throwIfParentInstance("addCrossProfileCalendarPackage"); - if (mService != null) { - try { - mService.addCrossProfileCalendarPackage(admin, packageName); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - - /** - * Removes a package that was allowed to access cross profile calendar APIs - * from the whitelist. - * - * <p>Called by a profile owner of a managed profile. + * <p>Calling with a null value for the set disables the restriction so that all packages + * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows + * all packages from accessing cross-profile calendar APIs. If this method isn't called, + * no package will be allowed to access cross-profile calendar APIs by default. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName name of the package to be removed from the whitelist. - * @return {@code true} if the package is successfully removed from the whitelist, - * {@code false} otherwise. + * @param packageNames set of packages to be whitelisted. * @throws SecurityException if {@code admin} is not a profile owner. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) * @see #getCrossProfileCalendarPackages(ComponentName) */ - public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin, - @NonNull String packageName) { - throwIfParentInstance("removeCrossProfileCalendarPackage"); + public void setCrossProfileCalendarPackages(@NonNull ComponentName admin, + @Nullable Set<String> packageNames) { + throwIfParentInstance("setCrossProfileCalendarPackages"); if (mService != null) { try { - return mService.removeCrossProfileCalendarPackage(admin, packageName); + mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null + : new ArrayList<>(packageNames)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - return false; } /** - * Gets a set of package names that are whitelisted to access cross profile calendar APIs. + * Gets a set of package names that are whitelisted to access cross-profile calendar APIs. * * <p>Called by a profile owner of a managed profile. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @return the set of names of packages that were previously whitelisted via - * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an + * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an * empty set if none have been whitelisted. * @throws SecurityException if {@code admin} is not a profile owner. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) */ - public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) { + public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) { throwIfParentInstance("getCrossProfileCalendarPackages"); if (mService != null) { try { - return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin)); + final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin); + return packageNames == null ? null : new ArraySet<>(packageNames); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -10402,22 +10493,21 @@ public class DevicePolicyManager { } /** - * Returns if a package is whitelisted to access cross profile calendar APIs. + * Returns if a package is whitelisted to access cross-profile calendar APIs. * * <p>To query for a specific user, use * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for * that user, and get a {@link DevicePolicyManager} from this context. * * @param packageName the name of the package - * @return {@code true} if the package is whitelisted to access cross profile calendar APIs. + * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs. * {@code false} otherwise. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) * @see #getCrossProfileCalendarPackages(ComponentName) * @hide */ - public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) { + public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) { throwIfParentInstance("isPackageAllowedToAccessCalendar"); if (mService != null) { try { @@ -10431,27 +10521,27 @@ public class DevicePolicyManager { } /** - * Gets a set of package names that are whitelisted to access cross profile calendar APIs. + * Gets a set of package names that are whitelisted to access cross-profile calendar APIs. * * <p>To query for a specific user, use * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for * that user, and get a {@link DevicePolicyManager} from this context. * * @return the set of names of packages that were previously whitelisted via - * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an + * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an * empty set if none have been whitelisted. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) * @see #getCrossProfileCalendarPackages(ComponentName) * @hide */ - public @NonNull Set<String> getCrossProfileCalendarPackages() { + public @Nullable Set<String> getCrossProfileCalendarPackages() { throwIfParentInstance("getCrossProfileCalendarPackages"); if (mService != null) { try { - return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser( - myUserId())); + final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser( + myUserId()); + return packageNames == null ? null : new ArraySet<>(packageNames); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 568becfcdd1a..1751a91caf1a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -424,8 +424,7 @@ interface IDevicePolicyManager { void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener); - void addCrossProfileCalendarPackage(in ComponentName admin, String packageName); - boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName); + void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames); List<String> getCrossProfileCalendarPackages(in ComponentName admin); boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle); List<String> getCrossProfileCalendarPackagesForUser(int userHandle); diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 8b41755f6dec..e5df2c70eeec 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -20,6 +20,11 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,6 +32,8 @@ import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -85,6 +92,101 @@ public class PasswordMetrics implements Parcelable { nonLetter = in.readInt(); } + /** Returns the min quality allowed by {@code complexityLevel}. */ + public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) { + // this would be the quality of the first metrics since mMetrics is sorted in ascending + // order of quality + return PasswordComplexityBucket + .complexityLevelToBucket(complexityLevel).mMetrics[0].quality; + } + + /** + * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet + * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code + * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements, + * and {@code complexityLevel}. + * + * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are + * more than one set of metrics to meet the minimum complexity requirement and inspecting what + * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics + * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or + * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI + * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering + * an alphanumeric password so we would update the min complexity required min length to 6. + */ + public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel, + int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric, + boolean requiresLettersOrSymbols) { + int targetQuality = Math.max( + userEnteredPasswordQuality, + getActualRequiredQuality( + requestedQuality, requiresNumeric, requiresLettersOrSymbols)); + return getTargetQualityMetrics(complexityLevel, targetQuality); + } + + /** + * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality + * is the same as {@code targetQuality}. + * + * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics + * with the min quality at {@code complexityLevel}. + */ + // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private + @VisibleForTesting + public static PasswordMetrics getTargetQualityMetrics( + @PasswordComplexity int complexityLevel, int targetQuality) { + PasswordComplexityBucket targetBucket = + PasswordComplexityBucket.complexityLevelToBucket(complexityLevel); + for (PasswordMetrics metrics : targetBucket.mMetrics) { + if (targetQuality == metrics.quality) { + return metrics; + } + } + // none of the metrics at complexityLevel has targetQuality, return metrics with min quality + // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where + // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and + // requested quality is NUMERIC + return targetBucket.mMetrics[0]; + } + + /** + * Finds out the actual quality requirement based on whether quality is {@link + * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are + * required. + */ + @VisibleForTesting + // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private + public static int getActualRequiredQuality( + int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) { + if (requestedQuality != PASSWORD_QUALITY_COMPLEX) { + return requestedQuality; + } + + // find out actual password quality from complex requirements + if (requiresNumeric && requiresLettersOrSymbols) { + return PASSWORD_QUALITY_ALPHANUMERIC; + } + if (requiresLettersOrSymbols) { + return PASSWORD_QUALITY_ALPHABETIC; + } + if (requiresNumeric) { + // cannot specify numeric complex using complex quality so this must be numeric + return PASSWORD_QUALITY_NUMERIC; + } + + // reaching here means dpm sets quality to complex without specifying any requirements + return PASSWORD_QUALITY_UNSPECIFIED; + } + + /** + * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE} + * if {@code complexityLevel} is not valid. + */ + @PasswordComplexity + public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) { + return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel; + } + public boolean isDefault() { return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 @@ -280,7 +382,7 @@ public class PasswordMetrics implements Parcelable { @PasswordComplexity public int determineComplexity() { for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { - if (satisfiesBucket(bucket.getMetrics())) { + if (satisfiesBucket(bucket.mMetrics)) { return bucket.mComplexityLevel; } } @@ -290,7 +392,7 @@ public class PasswordMetrics implements Parcelable { /** * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. */ - public static class PasswordComplexityBucket { + private static class PasswordComplexityBucket { /** * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of * {@link PasswordMetrics}. @@ -299,12 +401,13 @@ public class PasswordMetrics implements Parcelable { new PasswordComplexityBucket( PASSWORD_COMPLEXITY_HIGH, new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6), + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 8), new PasswordMetrics( DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ - 8)); + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ + 6)); /** * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of @@ -314,11 +417,12 @@ public class PasswordMetrics implements Parcelable { new PasswordComplexityBucket( PASSWORD_COMPLEXITY_MEDIUM, new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4), + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 4), new PasswordMetrics( DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4)); /** @@ -328,11 +432,11 @@ public class PasswordMetrics implements Parcelable { private static final PasswordComplexityBucket LOW = new PasswordComplexityBucket( PASSWORD_COMPLEXITY_LOW, - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING), new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)); + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC)); /** * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. @@ -348,19 +452,27 @@ public class PasswordMetrics implements Parcelable { private final int mComplexityLevel; private final PasswordMetrics[] mMetrics; + /** + * @param metricsArray must be sorted in ascending order of {@link #quality}. + */ private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, - PasswordMetrics... metrics) { + PasswordMetrics... metricsArray) { + int previousQuality = PASSWORD_QUALITY_UNSPECIFIED; + for (PasswordMetrics metrics : metricsArray) { + if (metrics.quality < previousQuality) { + throw new IllegalArgumentException("metricsArray must be sorted in ascending" + + " order of quality"); + } + previousQuality = metrics.quality; + } + + this.mMetrics = metricsArray; this.mComplexityLevel = complexityLevel; - this.mMetrics = metrics; - } - /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */ - public PasswordMetrics[] getMetrics() { - return mMetrics; } /** Returns the bucket that {@code complexityLevel} represents. */ - public static PasswordComplexityBucket complexityLevelToBucket( + private static PasswordComplexityBucket complexityLevelToBucket( @PasswordComplexity int complexityLevel) { for (PasswordComplexityBucket bucket : BUCKETS) { if (bucket.mComplexityLevel == complexityLevel) { diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 7d03f00611d4..6006ad2f5ed3 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -894,7 +894,7 @@ public class AssistStructure implements Parcelable { } if (mAutofillId != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID; - if (mAutofillId.isVirtual()) { + if (mAutofillId.isVirtualInt()) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID; } } @@ -961,8 +961,9 @@ public class AssistStructure implements Parcelable { if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { out.writeInt(mAutofillId.getViewId()); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) { - out.writeInt(mAutofillId.getVirtualChildId()); + out.writeInt(mAutofillId.getVirtualChildIntId()); } + // TODO(b/113593220): write session id as well } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) { out.writeInt(mAutofillType); diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index c983d4f68710..24580b40aa29 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -16,6 +16,7 @@ package android.app.backup; +import android.annotation.Nullable; import android.app.IBackupAgent; import android.app.QueuedWork; import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; @@ -181,6 +182,8 @@ public abstract class BackupAgent extends ContextWrapper { Handler mHandler = null; + @Nullable private UserHandle mUser; + Handler getHandler() { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); @@ -232,6 +235,8 @@ public abstract class BackupAgent extends ContextWrapper { */ public void onCreate(UserHandle user) { onCreate(); + + mUser = user; } /** @@ -528,6 +533,10 @@ public abstract class BackupAgent extends ContextWrapper { public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { } + private int getBackupUserId() { + return mUser == null ? super.getUserId() : mUser.getIdentifier(); + } + /** * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>. * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path @@ -1033,7 +1042,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token, 0); + callbackBinder.opCompleteForUser(getBackupUserId(), token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -1082,7 +1091,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token, 0); + callbackBinder.opCompleteForUser(getBackupUserId(), token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -1112,7 +1121,8 @@ public abstract class BackupAgent extends ContextWrapper { } finally { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token, measureOutput.getSize()); + callbackBinder.opCompleteForUser(getBackupUserId(), token, + measureOutput.getSize()); } catch (RemoteException e) { // timeout, so we're safe } @@ -1137,7 +1147,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token, 0); + callbackBinder.opCompleteForUser(getBackupUserId(), token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -1162,7 +1172,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token, 0); + callbackBinder.opCompleteForUser(getBackupUserId(), token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index f8c5a815a32e..eda8981d7e0e 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -549,6 +549,19 @@ interface IBackupManager { /** * Notify the backup manager that a BackupAgent has completed the operation + * corresponding to the given token and user id. + * + * @param userId User id for which the operation has been completed. + * @param token The transaction token passed to the BackupAgent method being + * invoked. + * @param result In the case of a full backup measure operation, the estimated + * total file size that would result from the operation. Unused in all other + * cases. + */ + void opCompleteForUser(int userId, int token, long result); + + /** + * Notify the backup manager that a BackupAgent has completed the operation * corresponding to the given token. * * @param token The transaction token passed to the BackupAgent method being diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java index 3f715186abfb..9bb39e599135 100644 --- a/core/java/android/app/contentsuggestions/ClassificationsRequest.java +++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java @@ -26,6 +26,11 @@ import android.os.Parcelable; import java.util.List; /** + * Request object used when asking {@link ContentSuggestionsManager} to classify content selections. + * + * <p>The request contains a list of {@link ContentSelection} objects to be classified along with + * implementation specific extras. + * * @hide */ @SystemApi @@ -44,14 +49,14 @@ public final class ClassificationsRequest implements Parcelable { /** * Return request selections. */ - public List<ContentSelection> getSelections() { + public @NonNull List<ContentSelection> getSelections() { return mSelections; } /** - * Return the request extras. + * Return the request extras or {@code null} if there are none. */ - public Bundle getExtras() { + public @Nullable Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java index f18520f1053c..2a00b406f814 100644 --- a/core/java/android/app/contentsuggestions/ContentClassification.java +++ b/core/java/android/app/contentsuggestions/ContentClassification.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; /** + * Represents the classification of a content suggestion. The result of a + * {@link ClassificationsRequest} to {@link ContentSuggestionsManager}. + * * @hide */ @SystemApi @@ -32,6 +35,12 @@ public final class ContentClassification implements Parcelable { @NonNull private final Bundle mExtras; + /** + * Default constructor. + * + * @param classificationId implementation specific id for the selection / classification. + * @param extras containing the classification data. + */ public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) { mClassificationId = classificationId; mExtras = extras; @@ -40,14 +49,14 @@ public final class ContentClassification implements Parcelable { /** * Return the classification id. */ - public String getId() { + public @NonNull String getId() { return mClassificationId; } /** * Return the classification extras. */ - public Bundle getExtras() { + public @NonNull Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java index a8917f782e83..16b4f3f6456d 100644 --- a/core/java/android/app/contentsuggestions/ContentSelection.java +++ b/core/java/android/app/contentsuggestions/ContentSelection.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; /** + * Represents a suggested selection within a set of on screen content. The result of a + * {@link SelectionsRequest} to {@link ContentSuggestionsManager}. + * * @hide */ @SystemApi @@ -32,6 +35,12 @@ public final class ContentSelection implements Parcelable { @NonNull private final Bundle mExtras; + /** + * Default constructor. + * + * @param selectionId implementation specific id for the selection. + * @param extras containing the data that represents the selection. + */ public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) { mSelectionId = selectionId; mExtras = extras; @@ -40,14 +49,14 @@ public final class ContentSelection implements Parcelable { /** * Return the selection id. */ - public String getId() { + public @NonNull String getId() { return mSelectionId; } /** * Return the selection extras. */ - public Bundle getExtras() { + public @NonNull Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java index 16f3e6b35aa4..e3c8bc5c7e9e 100644 --- a/core/java/android/app/contentsuggestions/SelectionsRequest.java +++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java @@ -25,6 +25,12 @@ import android.os.Parcel; import android.os.Parcelable; /** + * The request object used to request content selections from {@link ContentSuggestionsManager}. + * + * <p>Selections are requested for a given taskId as specified by + * {@link android.app.ActivityManager} and optionally take an interest point that specifies the + * point on the screen that should be considered as the most important. + * * @hide */ @SystemApi @@ -49,16 +55,17 @@ public final class SelectionsRequest implements Parcelable { } /** - * Return the request point of interest. + * Return the request point of interest or {@code null} if there is no point of interest for + * this request. */ - public Point getInterestPoint() { + public @Nullable Point getInterestPoint() { return mInterestPoint; } /** - * Return the request extras. + * Return the request extras or {@code null} if there aren't any. */ - public Bundle getExtras() { + public @Nullable Bundle getExtras() { return mExtras; } @@ -99,6 +106,11 @@ public final class SelectionsRequest implements Parcelable { private Point mInterestPoint; private Bundle mExtras; + /** + * Default constructor. + * + * @param taskId of the type used by {@link android.app.ActivityManager} + */ public Builder(int taskId) { mTaskId = taskId; } diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index a6abe0b30f79..ddd531339d39 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -172,6 +172,15 @@ public final class RoleManager { public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP"; /** + * The name of the assistant app role. + * + * @hide + */ + @SystemApi + @TestApi + public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; + + /** * The action used to request user approval of a role for an application. * * @hide diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index bbae7d3463ae..b1500c193820 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -55,8 +55,13 @@ interface IUsageStatsManager { long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent, in PendingIntent sessionEndCallbackIntent, String callingPackage); void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage); + void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs, + in PendingIntent callback, String callingPackage); + void unregisterAppUsageLimitObserver(int observerId, String callingPackage); void reportUsageStart(in IBinder activity, String token, String callingPackage); void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs, String callingPackage); void reportUsageStop(in IBinder activity, String token, String callingPackage); + int getUsageSource(); + void forceUsageSourceSettingRead(); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 2c5fe046faad..451f44b93c32 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -16,6 +16,7 @@ package android.app.usage; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.content.res.Configuration; @@ -286,7 +287,6 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String mClass; - /** * {@hide} */ @@ -295,6 +295,16 @@ public final class UsageEvents implements Parcelable { /** * {@hide} */ + public String mTaskRootPackage; + + /** + * {@hide} + */ + public String mTaskRootClass; + + /** + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long mTimeStamp; @@ -373,6 +383,8 @@ public final class UsageEvents implements Parcelable { mPackage = orig.mPackage; mClass = orig.mClass; mInstanceId = orig.mInstanceId; + mTaskRootPackage = orig.mTaskRootPackage; + mTaskRootClass = orig.mTaskRootClass; mTimeStamp = orig.mTimeStamp; mEventType = orig.mEventType; mConfiguration = orig.mConfiguration; @@ -411,6 +423,28 @@ public final class UsageEvents implements Parcelable { } /** + * The package name of the task root when this event was reported. + * Or {@code null} for queries from apps without {@link + * android.Manifest.permission#PACKAGE_USAGE_STATS} + * @hide + */ + @SystemApi + public @Nullable String getTaskRootPackageName() { + return mTaskRootPackage; + } + + /** + * The class name of the task root when this event was reported. + * Or {@code null} for queries from apps without {@link + * android.Manifest.permission#PACKAGE_USAGE_STATS} + * @hide + */ + @SystemApi + public @Nullable String getTaskRootClassName() { + return mTaskRootClass; + } + + /** * The time at which this event occurred, measured in milliseconds since the epoch. * <p/> * See {@link System#currentTimeMillis()}. @@ -522,6 +556,9 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mIndex = 0; + // Only used when parceling events. If false, task roots will be omitted from the parcel + private final boolean mIncludeTaskRoots; + /* * In order to save space, since ComponentNames will be duplicated everywhere, * we use a map and index into it. @@ -552,6 +589,7 @@ public final class UsageEvents implements Parcelable { mParcel.setDataSize(mParcel.dataPosition()); mParcel.setDataPosition(positionInParcel); } + mIncludeTaskRoots = true; } /** @@ -560,16 +598,27 @@ public final class UsageEvents implements Parcelable { */ UsageEvents() { mEventCount = 0; + mIncludeTaskRoots = true; } /** * Construct the iterator in preparation for writing it to a parcel. + * Defaults to excluding task roots from the parcel. * {@hide} */ public UsageEvents(List<Event> events, String[] stringPool) { + this(events, stringPool, false); + } + + /** + * Construct the iterator in preparation for writing it to a parcel. + * {@hide} + */ + public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) { mStringPool = stringPool; mEventCount = events.size(); mEventsToWrite = events; + mIncludeTaskRoots = includeTaskRoots; } /** @@ -645,9 +694,25 @@ public final class UsageEvents implements Parcelable { } else { classIndex = -1; } + + final int taskRootPackageIndex; + if (mIncludeTaskRoots && event.mTaskRootPackage != null) { + taskRootPackageIndex = findStringIndex(event.mTaskRootPackage); + } else { + taskRootPackageIndex = -1; + } + + final int taskRootClassIndex; + if (mIncludeTaskRoots && event.mTaskRootClass != null) { + taskRootClassIndex = findStringIndex(event.mTaskRootClass); + } else { + taskRootClassIndex = -1; + } p.writeInt(packageIndex); p.writeInt(classIndex); p.writeInt(event.mInstanceId); + p.writeInt(taskRootPackageIndex); + p.writeInt(taskRootClassIndex); p.writeInt(event.mEventType); p.writeLong(event.mTimeStamp); @@ -691,6 +756,21 @@ public final class UsageEvents implements Parcelable { eventOut.mClass = null; } eventOut.mInstanceId = p.readInt(); + + final int taskRootPackageIndex = p.readInt(); + if (taskRootPackageIndex >= 0) { + eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex]; + } else { + eventOut.mTaskRootPackage = null; + } + + final int taskRootClassIndex = p.readInt(); + if (taskRootClassIndex >= 0) { + eventOut.mTaskRootClass = mStringPool[taskRootClassIndex]; + } else { + eventOut.mTaskRootClass = null; + } + eventOut.mEventType = p.readInt(); eventOut.mTimeStamp = p.readLong(); diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 605deac80994..51397a243420 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.PendingIntent; @@ -234,6 +235,29 @@ public final class UsageStatsManager { @SystemApi public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED"; + + /** + * App usage observers will consider the task root package the source of usage. + * @hide + */ + @SystemApi + public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; + + /** + * App usage observers will consider the visible activity's package the source of usage. + * @hide + */ + @SystemApi + public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; + + /** @hide */ + @IntDef(prefix = { "USAGE_SOURCE_" }, value = { + USAGE_SOURCE_TASK_ROOT_ACTIVITY, + USAGE_SOURCE_CURRENT_ACTIVITY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UsageSource {} + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static final UsageEvents sEmptyResults = new UsageEvents(); @@ -595,7 +619,7 @@ public final class UsageStatsManager { * @param timeLimit The total time the set of apps can be in the foreground before the * callbackIntent is delivered. Must be at least one minute. * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. - * @param callbackIntent The PendingIntent that will be dispatched when the time limit is + * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is * exceeded by the group of apps. The delivered Intent will also contain * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and * {@link #EXTRA_TIME_USED}. Cannot be null. @@ -658,14 +682,14 @@ public final class UsageStatsManager { * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}. * Cannot be null. * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the - * time limit is exceeded by the group of apps. The delivered - * Intent will also contain the extras {@link + * usage limit is exceeded by the group of apps. The + * delivered Intent will also contain the extras {@link * #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link * #EXTRA_TIME_USED}. Cannot be null. * @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the - * session has ended after the time limit has been exceeded. The - * session is considered at its end after the {@code observed} - * usage has stopped and an additional {@code + * session has ended after the usage limit has been exceeded. + * The session is considered at its end after the {@code + * observed} usage has stopped and an additional {@code * sessionThresholdTime} has passed. The delivered Intent will * also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link * #EXTRA_TIME_USED}. Can be null. @@ -712,6 +736,74 @@ public final class UsageStatsManager { } /** + * Register a usage limit observer that receives a callback on the provided intent when the + * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the + * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting + * package's name and a token that the calling app will use, separated by the forward slash + * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N + * <p> + * Registering an {@code observerId} that was already registered will override the previous one. + * No more than 1000 unique {@code observerId} may be registered by a single uid + * at any one time. + * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver} + * <p> + * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here + * will be visible to the launcher so that it can report the limit to the user and how much + * of it is remaining. + * @see android.content.pm.LauncherApps#getAppUsageLimit + * + * @param observerId A unique id associated with the group of apps to be monitored. There can + * be multiple groups with common packages and different time limits. + * @param observedEntities The list of packages and token to observe for usage time. Cannot be + * null and must include at least one package or token. + * @param timeLimit The total time the set of apps can be in the foreground before the + * callbackIntent is delivered. Must be at least one minute. + * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. + * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is + * exceeded by the group of apps. The delivered Intent will also contain + * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and + * {@link #EXTRA_TIME_USED}. Cannot be null. + * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE + * permissions. + * @hide + */ + @SystemApi + @RequiresPermission(allOf = { + android.Manifest.permission.SUSPEND_APPS, + android.Manifest.permission.OBSERVE_APP_USAGE}) + public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities, + long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) { + try { + mService.registerAppUsageLimitObserver(observerId, observedEntities, + timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregister the app usage limit observer specified by the {@code observerId}. + * This will only apply to any observer registered by this application. Unregistering + * an observer that was already unregistered or never registered will have no effect. + * + * @param observerId The id of the observer that was previously registered. + * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE + * permissions. + * @hide + */ + @SystemApi + @RequiresPermission(allOf = { + android.Manifest.permission.SUSPEND_APPS, + android.Manifest.permission.OBSERVE_APP_USAGE}) + public void unregisterAppUsageLimitObserver(int observerId) { + try { + mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Report usage associated with a particular {@code token} has started. Tokens are app defined * strings used to represent usage of in-app features. Apps with the {@link * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers @@ -719,6 +811,7 @@ public final class UsageStatsManager { * and usage will be considered stopped if the activity stops or crashes. * @see #registerAppUsageObserver * @see #registerUsageSessionObserver + * @see #registerAppUsageLimitObserver * * @param activity The activity {@code token} is associated with. * @param token The token to report usage against. @@ -742,6 +835,7 @@ public final class UsageStatsManager { * {@code activity} and usage will be considered stopped if the activity stops or crashes. * @see #registerAppUsageObserver * @see #registerUsageSessionObserver + * @see #registerAppUsageLimitObserver * * @param activity The activity {@code token} is associated with. * @param token The token to report usage against. @@ -776,6 +870,38 @@ public final class UsageStatsManager { } } + /** + * Get what App Usage Observers will consider the source of usage for an activity. Usage Source + * is decided at boot and will not change until next boot. + * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY + * @see #USAGE_SOURCE_CURRENT_ACTIVITY + * + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and + * is not the profile owner of this user. + * @hide + */ + @SystemApi + public @UsageSource int getUsageSource() { + try { + return mService.getUsageSource(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Force the Usage Source be reread from global settings. + * @hide + */ + @TestApi + public void forceUsageSourceSettingRead() { + try { + mService.forceUsageSourceSettingRead(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public static String reasonToString(int standbyReason) { StringBuilder sb = new StringBuilder(); @@ -845,6 +971,22 @@ public final class UsageStatsManager { return sb.toString(); } + /** @hide */ + public static String usageSourceToString(int usageSource) { + switch (usageSource) { + case USAGE_SOURCE_TASK_ROOT_ACTIVITY: + return "TASK_ROOT_ACTIVITY"; + case USAGE_SOURCE_CURRENT_ACTIVITY: + return "CURRENT_ACTIVITY"; + default: + StringBuilder sb = new StringBuilder(); + sb.append("UNKNOWN("); + sb.append(usageSource); + sb.append(")"); + return sb.toString(); + } + } + /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index cc3ab0025864..3d3c03ae3daa 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -20,6 +20,7 @@ import android.annotation.UserIdInt; import android.app.usage.UsageStatsManager.StandbyBuckets; import android.content.ComponentName; import android.content.res.Configuration; +import android.os.UserHandle; import java.util.List; import java.util.Set; @@ -40,9 +41,11 @@ public abstract class UsageStatsManagerInternal { * {@link UsageEvents} * @param instanceId For activity, hashCode of ActivityRecord's appToken. * For non-activity, it is not used. + * @param taskRoot For activity, the name of the package at the root of the task + * For non-activity, it is not used. */ public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType, - int instanceId); + int instanceId, ComponentName taskRoot); /** * Reports an event to the UsageStatsManager. @@ -268,4 +271,40 @@ public abstract class UsageStatsManagerInternal { * @param userId which user the app is associated with */ public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId); + + /** + * Returns an object describing the app usage limit for the given package which was set via + * {@link UsageStatsManager#registerAppUsageLimitObserver}. + * If there are multiple limits that apply to the package, the one with the smallest + * time remaining will be returned. + * + * @param packageName name of the package whose app usage limit will be returned + * @param user the user associated with the limit + * @return an {@link AppUsageLimitData} object describing the app time limit containing + * the given package, with the smallest time remaining. + */ + public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user); + + /** A class which is used to share the usage limit data for an app or a group of apps. */ + public static class AppUsageLimitData { + private final boolean mGroupLimit; + private final long mTotalUsageLimit; + private final long mUsageRemaining; + + public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) { + this.mGroupLimit = groupLimit; + this.mTotalUsageLimit = totalUsageLimit; + this.mUsageRemaining = usageRemaining; + } + + public boolean isGroupLimit() { + return mGroupLimit; + } + public long getTotalUsageLimit() { + return mTotalUsageLimit; + } + public long getUsageRemaining() { + return mUsageRemaining; + } + } } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 171c2f5b1a08..c4bf1ebd1a6f 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -434,7 +434,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override - public int getConnectionState(BluetoothDevice device) { + public @BtProfileState int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); try { mServiceLock.readLock().lock(); @@ -689,7 +689,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage - public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); try { mServiceLock.readLock().lock(); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 1945b2fd51de..ab8c196edccd 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -36,6 +36,7 @@ import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.BatteryStats; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -648,6 +649,32 @@ public final class BluetoothAdapter { private final Object mLock = new Object(); private final Map<LeScanCallback, ScanCallback> mLeScanClients; + private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>> + sMetadataListeners = new HashMap<>(); + + /** + * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener + * implementation. + */ + private static final IBluetoothMetadataListener sBluetoothMetadataListener = + new IBluetoothMetadataListener.Stub() { + @Override + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device); + for (Pair<MetadataListener, Handler> pair : list) { + MetadataListener listener = pair.first; + Handler handler = pair.second; + handler.post(() -> { + listener.onMetadataChanged(device, key, value); + }); + } + } + } + return; + } + }; /** * Get a handle to the default local Bluetooth adapter. @@ -1873,6 +1900,20 @@ public final class BluetoothAdapter { } /** + * Return true if Hearing Aid Profile is supported. + * + * @return true if phone supports Hearing Aid Profile + */ + private boolean isHearingAidProfileSupported() { + try { + return mManagerService.isHearingAidProfileSupported(); + } catch (RemoteException e) { + Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e); + return false; + } + } + + /** * Get the maximum number of connected audio devices. * * @return the maximum number of connected audio devices @@ -2024,6 +2065,11 @@ public final class BluetoothAdapter { supportedProfiles.add(i); } } + } else { + // Bluetooth is disabled. Just fill in known supported Profiles + if (isHearingAidProfileSupported()) { + supportedProfiles.add(BluetoothProfile.HEARING_AID); + } } } } catch (RemoteException e) { @@ -2441,15 +2487,16 @@ public final class BluetoothAdapter { * Get the profile proxy object associated with the profile. * * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must - * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status - * and to get the proxy object. + * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link + * BluetoothProfile#GATT_SERVER}. Clients must implement {@link + * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the + * proxy object. * * @param context Context of the application * @param listener The service Listener for connection callbacks. * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or - * {@link BluetoothProfile#GATT_SERVER}. + * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link + * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. * @return true on success, false on error */ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, @@ -2498,8 +2545,11 @@ public final class BluetoothAdapter { BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener); return true; } else if (profile == BluetoothProfile.HEARING_AID) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); - return true; + if (isHearingAidProfileSupported()) { + BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); + return true; + } + return false; } else { return false; } @@ -2607,6 +2657,16 @@ public final class BluetoothAdapter { } } } + synchronized (sMetadataListeners) { + sMetadataListeners.forEach((device, pair) -> { + try { + mService.registerMetadataListener(sBluetoothMetadataListener, + device); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register metadata listener", e); + } + }); + } } public void onBluetoothServiceDown() { @@ -3090,4 +3150,142 @@ public final class BluetoothAdapter { + "listenUsingInsecureL2capChannel"); return listenUsingInsecureL2capChannel(); } + + /** + * Register a {@link #MetadataListener} to receive update about metadata + * changes for this {@link BluetoothDevice}. + * Registration must be done when Bluetooth is ON and will last until + * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth + * restarted in the middle. + * All input parameters should not be null or {@link NullPointerException} will be triggered. + * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered + * once, double registration would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be registered + * @param listener {@link #MetadataListener} that will receive asynchronous callbacks + * @param handler the handler for listener callback + * @return true on success, false on error + * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler} + * is null. + * @throws IllegalArgumentException The same {@link #MetadataListener} and + * {@link BluetoothDevice} are registered twice. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener, + Handler handler) { + if (DBG) Log.d(TAG, "registerMetdataListener()"); + + final IBluetooth service = mService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener"); + return false; + } + if (listener == null) { + throw new NullPointerException("listener is null"); + } + if (device == null) { + throw new NullPointerException("device is null"); + } + if (handler == null) { + throw new NullPointerException("handler is null"); + } + + synchronized (sMetadataListeners) { + List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device); + if (listenerList == null) { + // Create new listener/handler list for registeration + listenerList = new ArrayList<>(); + sMetadataListeners.put(device, listenerList); + } else { + // Check whether this device was already registed by the lisenter + if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { + throw new IllegalArgumentException("listener was already regestered" + + " for the device"); + } + } + + Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler); + listenerList.add(listenerPair); + + boolean ret = false; + try { + ret = service.registerMetadataListener(sBluetoothMetadataListener, device); + } catch (RemoteException e) { + Log.e(TAG, "registerMetadataListener fail", e); + } finally { + if (!ret) { + // Remove listener registered earlier when fail. + listenerList.remove(listenerPair); + if (listenerList.isEmpty()) { + // Remove the device if its listener list is empty + sMetadataListeners.remove(device); + } + } + } + return ret; + } + } + + /** + * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}. + * Unregistration can be done when Bluetooth is either ON or OFF. + * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must + * be called before unregisteration. + * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be unregistered. it + * should not be null or {@link NullPointerException} will be triggered. + * @return true on success, false on error + * @throws NullPointerException If {@code device} is null. + * @throws IllegalArgumentException If {@code device} has not been registered before. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean unregisterMetadataListener(BluetoothDevice device) { + if (DBG) Log.d(TAG, "unregisterMetdataListener()"); + if (device == null) { + throw new NullPointerException("device is null"); + } + + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + sMetadataListeners.remove(device); + } else { + throw new IllegalArgumentException("device was not registered"); + } + + final IBluetooth service = mService; + if (service == null) { + // Bluetooth is OFF, do nothing to Bluetooth service. + return true; + } + try { + return service.unregisterMetadataListener(device); + } catch (RemoteException e) { + Log.e(TAG, "unregisterMetadataListener fail", e); + return false; + } + } + } + + /** + * This abstract class is used to implement {@link BluetoothAdapter} metadata listener. + * @hide + */ + @SystemApi + public abstract static class MetadataListener { + /** + * Callback triggered if the metadata of {@link BluetoothDevice} registered in + * {@link #registerMetadataListener}. + * + * @param device changed {@link BluetoothDevice}. + * @param key changed metadata key, one of BluetoothDevice.METADATA_*. + * @param value the new value of metadata. + */ + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + } + } } diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java index 78560d2de420..2cb7b2d3c7e9 100644 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ b/core/java/android/bluetooth/BluetoothCodecStatus.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -42,7 +43,7 @@ public final class BluetoothCodecStatus implements Parcelable { public static final String EXTRA_CODEC_STATUS = "android.bluetooth.codec.extra.CODEC_STATUS"; - private final BluetoothCodecConfig mCodecConfig; + private final @Nullable BluetoothCodecConfig mCodecConfig; private final BluetoothCodecConfig[] mCodecsLocalCapabilities; private final BluetoothCodecConfig[] mCodecsSelectableCapabilities; @@ -140,7 +141,7 @@ public final class BluetoothCodecStatus implements Parcelable { * @return the current codec configuration */ @UnsupportedAppUsage - public BluetoothCodecConfig getCodecConfig() { + public @Nullable BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 235dc5c59c2a..4d8dc35d7148 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -341,6 +341,137 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.SDP_RECORD"; /** + * Maximum length of a metadata entry, this is to avoid exploding Bluetooth + * disk usage + * @hide + */ + @SystemApi + public static final int METADATA_MAX_LENGTH = 2048; + + /** + * Manufacturer name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MANUFACTURER_NAME = 0; + + /** + * Model name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MODEL_NAME = 1; + + /** + * Software version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_SOFTWARE_VERSION = 2; + + /** + * Hardware version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_HARDWARE_VERSION = 3; + + /** + * Package name of the companion app, if any + * @hide + */ + @SystemApi + public static final int METADATA_COMPANION_APP = 4; + + /** + * URI to the main icon shown on the settings UI + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_ICON = 5; + + /** + * Whether this device is an untethered headset with left, right and case + * @hide + */ + @SystemApi + public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; + + /** + * URI to icon of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; + + /** + * URI to icon of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; + + /** + * URI to icon of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_ICON = 9; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; + + /** + * Whether the left headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; + + /** + * Whether the right headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; + + /** + * Whether the headset charging case is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; + + /** + * URI to the enhanced settings UI slice, null or empty String means + * the UI does not exist + * @hide + */ + @SystemApi + public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -401,6 +532,28 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; /** + * Intent to broadcast silence mode changed. + * Alway contains the extra field {@link #EXTRA_DEVICE} + * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED} + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi + public static final String ACTION_SILENCE_MODE_CHANGED = + "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + + /** + * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent, + * contains whether device is in silence mode as boolean. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SILENCE_ENABLED = + "android.bluetooth.device.extra.SILENCE_ENABLED"; + + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. * * @hide @@ -1461,6 +1614,70 @@ public final class BluetoothDevice implements Parcelable { } /** + * Set the Bluetooth device silence mode. + * + * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} + * is an active device (for A2DP or HFP), the active device for that profile + * will be set to null. + * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP + * active device is null, the {@link BluetoothDevice} will be set as the + * active device for that profile. + * If the {@link BluetoothDevice} is disconnected, it exits silence mode. + * If the {@link BluetoothDevice} is set as the active device for A2DP or + * HFP, while silence mode is enabled, then the device will exit silence mode. + * If the {@link BluetoothDevice} is in silence mode, AVRCP position change + * event and HFP AG indicators will be disabled. + * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot + * enter silence mode. + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @param silence true to enter silence mode, false to exit + * @return true on success, false on error. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setSilenceMode(boolean silence) { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + if (getSilenceMode() == silence) { + return true; + } + return service.setSilenceMode(this, silence); + } catch (RemoteException e) { + Log.e(TAG, "setSilenceMode fail", e); + return false; + } + } + + /** + * Get the device silence mode status + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @return true on device in silence mode, otherwise false. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean getSilenceMode() { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + return service.getSilenceMode(this); + } catch (RemoteException e) { + Log.e(TAG, "getSilenceMode fail", e); + return false; + } + } + + /** * Sets whether the phonebook access is allowed to this device. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * @@ -2026,4 +2243,61 @@ public final class BluetoothDevice implements Parcelable { Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel"); return createInsecureL2capChannel(psm); } + + /** + * Set a keyed metadata of this {@link BluetoothDevice} to a + * {@link String} value. + * Only bonded devices's metadata will be persisted across Bluetooth + * restart. + * Metadata will be removed when the device's bond state is moved to + * {@link #BOND_NONE}. + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @param value the string data to set for key. Must be less than + * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length + * @return true on success, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setMetadata(int key, String value) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); + return false; + } + if (value.length() > METADATA_MAX_LENGTH) { + throw new IllegalArgumentException("value length is " + value.length() + + ", should not over " + METADATA_MAX_LENGTH); + } + try { + return service.setMetadata(this, key, value); + } catch (RemoteException e) { + Log.e(TAG, "setMetadata fail", e); + return false; + } + } + + /** + * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @return Metadata of the key as string, null on error or not found + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public String getMetadata(int key) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); + return null; + } + try { + return service.getMetadata(this, key); + } catch (RemoteException e) { + Log.e(TAG, "getMetadata fail", e); + return null; + } + } } diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index 6ed7942492b2..82cc1bcf053c 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -39,15 +39,14 @@ import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * This class provides the public APIs to control the Bluetooth Hearing Aid - * profile. + * This class provides the public APIs to control the Hearing Aid profile. * * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get * the BluetoothHearingAid proxy object. * - * <p> Each method is protected with its appropriate permission. - * @hide + * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each + * method is protected with its appropriate permission. */ public final class BluetoothHearingAid implements BluetoothProfile { private static final String TAG = "BluetoothHearingAid"; @@ -56,7 +55,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { /** * Intent used to broadcast the change in connection state of the Hearing Aid - * profile. + * profile. Please note that in the binaural case, there will be two different LE devices for + * the left and right side and each device will have their own connection state changes.S * * <p>This intent will have 3 extras: * <ul> @@ -77,27 +77,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; /** - * Intent used to broadcast the change in the Playing state of the Hearing Aid - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYING_STATE_CHANGED = - "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED"; - - /** * Intent used to broadcast the selection of a connected device as active. * * <p>This intent will have one extra: @@ -108,6 +87,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. + * + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage @@ -115,32 +96,38 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; /** - * Hearing Aid device is streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Left Hearing Aid. + * + * @hide */ - public static final int STATE_PLAYING = 10; + public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; /** - * Hearing Aid device is NOT streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Right Hearing Aid. + * + * @hide */ - public static final int STATE_NOT_PLAYING = 11; - - /** This device represents Left Hearing Aid. */ - public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; - - /** This device represents Right Hearing Aid. */ public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; - /** This device is Monaural. */ + /** + * This device is Monaural. + * + * @hide + */ public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; - /** This device is Binaural (should receive only left or right audio). */ + /** + * This device is Binaural (should receive only left or right audio). + * + * @hide + */ public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; - /** Can't read ClientID for this device */ + /** + * Indicates the HiSyncID could not be read and is unavailable. + * + * @hide + */ public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; private Context mContext; @@ -236,12 +223,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { } } - @Override - public void finalize() { - // The empty finalize needs to be kept or the - // cts signature tests would fail. - } - /** * Initiate connection to a profile of the remote bluetooth device. * @@ -538,10 +519,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { return "connected"; case STATE_DISCONNECTING: return "disconnecting"; - case STATE_PLAYING: - return "playing"; - case STATE_NOT_PLAYING: - return "not playing"; default: return "<unknown state " + state + ">"; } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 3c87c739e1f6..ef775967f8a7 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -18,11 +18,14 @@ package android.bluetooth; import android.Manifest; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -60,6 +63,16 @@ public interface BluetoothProfile { /** The profile is in disconnecting state */ int STATE_DISCONNECTING = 3; + /** @hide */ + @IntDef({ + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileState {} + /** * Headset and Handsfree profile */ @@ -185,7 +198,6 @@ public interface BluetoothProfile { /** * Hearing Aid Device * - * @hide */ int HEARING_AID = 21; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 6704a45fb07a..47a4a2dca73d 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -87,6 +87,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * <p>For more information about using a ContentResolver with content providers, read the * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> * developer guide.</p> + * </div> */ public abstract class ContentResolver implements ContentInterface { /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3c487a13af4b..22f73dbd4644 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2008,6 +2008,15 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.PERMISSION_GROUP_NAME"; /** + * Intent extra: The number of milliseconds. + * <p> + * Type: long + * </p> + */ + public static final String EXTRA_DURATION_MILLIS = + "android.intent.extra.DURATION_MILLIS"; + + /** * Activity action: Launch UI to review app uses of permissions. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name @@ -2020,11 +2029,16 @@ public class Intent implements Parcelable, Cloneable { * {@link #EXTRA_PERMISSION_NAME}. * </p> * <p> + * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent + * activity to show (optional). Must be non-negative. + * </p> + * <p> * Output: Nothing. * </p> * * @see #EXTRA_PERMISSION_NAME * @see #EXTRA_PERMISSION_GROUP_NAME + * @see #EXTRA_DURATION_MILLIS * * @hide */ @@ -2361,9 +2375,7 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PACKAGE_ENABLE_ROLLBACK = "android.intent.action.PACKAGE_ENABLE_ROLLBACK"; /** - * Broadcast Action: An existing version of an application package has been - * rolled back to a previous version. - * The data contains the name of the package. + * Broadcast Action: A rollback has been committed. * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -2372,8 +2384,8 @@ public class Intent implements Parcelable, Cloneable { */ @SystemApi @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = - "android.intent.action.PACKAGE_ROLLBACK_EXECUTED"; + public static final String ACTION_ROLLBACK_COMMITTED = + "android.intent.action.ROLLBACK_COMMITTED"; /** * @hide * Broadcast Action: Ask system services if there is any reason to @@ -3034,6 +3046,13 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE"; + /** + * Broadcast Action: Request the media scanner to scan a storage volume and add it to the media database. + * The path to the storage volume is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME"; + /** * Broadcast Action: The "Media Button" was pressed. Includes a single * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that @@ -9956,9 +9975,21 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ + public void writeToProto(ProtoOutputStream proto) { + // Same input parameters that toString() gives to toShortString(). + writeToProtoWithoutFieldId(proto, true, true, true, false); + } + + /** @hide */ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp, boolean extras, boolean clip) { long token = proto.start(fieldId); + writeToProtoWithoutFieldId(proto, secure, comp, extras, clip); + proto.end(token); + } + + private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp, + boolean extras, boolean clip) { if (mAction != null) { proto.write(IntentProto.ACTION, mAction); } @@ -10003,7 +10034,6 @@ public class Intent implements Parcelable, Cloneable { if (mSelector != null) { proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip)); } - proto.end(token); } /** diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 3cfbe0c6f08a..47034a6df8a7 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -508,7 +508,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { /** * Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case * an activity behind this can also be shown when locked. - * See android.R.attr#inheritShowWhenLocked + * See {@link android.R.attr#inheritShowWhenLocked}. * @hide */ public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 576466f21bcb..5d6d1444eaf3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1955,6 +1955,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0; } + /** @hide */ + public boolean isCodeIntegrityPreferred() { + return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0; + } + /** * Returns whether or not this application was installed as a virtual preload. */ diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index db2b6fd235d3..d1bc37791d40 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -23,6 +23,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; +import android.content.pm.LauncherApps; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -56,6 +57,9 @@ interface ILauncherApps { ApplicationInfo getApplicationInfo( String callingPackage, String packageName, int flags, in UserHandle user); + LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName, + in UserHandle user); + ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index c702b16c97f6..276853d3b860 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -76,4 +76,6 @@ interface IShortcutService { // System API used by framework's ShareSheet (ChooserActivity) ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId); + + boolean hasShareTargets(String packageName, String packageToCheck, int userId); }
\ No newline at end of file diff --git a/core/java/android/content/pm/LauncherApps.aidl b/core/java/android/content/pm/LauncherApps.aidl new file mode 100644 index 000000000000..1d98ad1abd32 --- /dev/null +++ b/core/java/android/content/pm/LauncherApps.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +parcelable LauncherApps.AppUsageLimit; diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 766c5660012b..89630e15972e 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -758,6 +758,27 @@ public class LauncherApps { } /** + * Returns an object describing the app usage limit for the given package. + * If there are multiple limits that apply to the package, the one with the smallest + * time remaining will be returned. + * + * @param packageName name of the package whose app usage limit will be returned + * @param user the user of the package + * + * @return an {@link AppUsageLimit} object describing the app time limit containing + * the given package with the smallest time remaining, or {@code null} if none exist. + * @throws SecurityException when the caller is not the active launcher. + */ + @Nullable + public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) { + try { + return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Checks if the activity exists and it enabled for a profile. * * @param component The activity to check. @@ -1632,4 +1653,86 @@ public class LauncherApps { return 0; } } + + /** + * A class that encapsulates information about the usage limit set for an app or + * a group of apps. + * + * <p>The launcher can query specifics about the usage limit such as if it is a group limit, + * how much usage time the limit has, and how much of the total usage time is remaining + * via the APIs available in this class. + * + * @see #getAppUsageLimit(String, UserHandle) + */ + public static final class AppUsageLimit implements Parcelable { + private final boolean mGroupLimit; + private final long mTotalUsageLimit; + private final long mUsageRemaining; + + /** @hide */ + public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) { + this.mGroupLimit = groupLimit; + this.mTotalUsageLimit = totalUsageLimit; + this.mUsageRemaining = usageRemaining; + } + + /** + * Returns whether this limit refers to a group of apps. + * + * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise. + * @hide + */ + public boolean isGroupLimit() { + return mGroupLimit; + } + + /** + * Returns the total usage limit in milliseconds set for an app or a group of apps. + * + * @return the total usage limit in milliseconds + */ + public long getTotalUsageLimit() { + return mTotalUsageLimit; + } + + /** + * Returns the usage remaining in milliseconds for an app or the group of apps + * this limit refers to. + * + * @return the usage remaining in milliseconds + */ + public long getUsageRemaining() { + return mUsageRemaining; + } + + private AppUsageLimit(Parcel source) { + mGroupLimit = source.readBoolean(); + mTotalUsageLimit = source.readLong(); + mUsageRemaining = source.readLong(); + } + + public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() { + @Override + public AppUsageLimit createFromParcel(Parcel source) { + return new AppUsageLimit(source); + } + + @Override + public AppUsageLimit[] newArray(int size) { + return new AppUsageLimit[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mGroupLimit); + dest.writeLong(mTotalUsageLimit); + dest.writeLong(mUsageRemaining); + } + } } diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java index 81e4105febee..7790067b03de 100644 --- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java +++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java @@ -25,12 +25,6 @@ import com.android.internal.annotations.VisibleForTesting; * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is * included by default. * - * <p>This is separated out so that it can be conditionally included at build time depending on - * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at - * build time, and remove org.apache.http.legacy from the bootclasspath pass - * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included - * and the - * * @hide */ @VisibleForTesting diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java index 03eefedd2b30..b19196a9b636 100644 --- a/core/java/android/content/pm/PackageBackwardCompatibility.java +++ b/core/java/android/content/pm/PackageBackwardCompatibility.java @@ -45,13 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { static { final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); - // Attempt to load and add the optional updater that will only be available when - // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that - // will remove any references to org.apache.http.library from the package so that it does - // not try and load the library when it is on the bootclasspath. - boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters, - "android.content.pm.OrgApacheHttpLegacyUpdater", - RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new); + // Automatically add the org.apache.http.legacy library to the app classpath if the app + // targets < P. + packageUpdaters.add(new OrgApacheHttpLegacyUpdater()); packageUpdaters.add(new AndroidHidlUpdater()); @@ -70,7 +66,7 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { PackageSharedLibraryUpdater[] updaterArray = packageUpdaters .toArray(new PackageSharedLibraryUpdater[0]); INSTANCE = new PackageBackwardCompatibility( - bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray); + bootClassPathContainsATB, updaterArray); } /** @@ -116,15 +112,12 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { return INSTANCE; } - private final boolean mBootClassPathContainsOAHL; - private final boolean mBootClassPathContainsATB; private final PackageSharedLibraryUpdater[] mPackageUpdaters; - public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL, + public PackageBackwardCompatibility( boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) { - this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL; this.mBootClassPathContainsATB = bootClassPathContainsATB; this.mPackageUpdaters = packageUpdaters; } @@ -148,14 +141,6 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { } /** - * True if the org.apache.http.legacy is on the bootclasspath, false otherwise. - */ - @VisibleForTesting - public static boolean bootClassPathContainsOAHL() { - return INSTANCE.mBootClassPathContainsOAHL; - } - - /** * True if the android.test.base is on the bootclasspath, false otherwise. */ @VisibleForTesting diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 94b7c4538c51..2dc014c45fad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1543,6 +1543,13 @@ public class PackageInstaller { this.isStaged = true; } + /** + * Set this session to be installing an APEX package. + */ + public void setInstallAsApex() { + installFlags |= PackageManager.INSTALL_APEX; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -1703,6 +1710,7 @@ public class PackageInstaller { /** {@hide} */ public boolean isSessionFailed; private int mStagedSessionErrorCode; + private String mStagedSessionErrorMessage; /** {@hide} */ @UnsupportedAppUsage @@ -1742,6 +1750,7 @@ public class PackageInstaller { isSessionReady = source.readBoolean(); isSessionFailed = source.readBoolean(); mStagedSessionErrorCode = source.readInt(); + mStagedSessionErrorMessage = source.readString(); } /** @@ -2059,9 +2068,19 @@ public class PackageInstaller { return mStagedSessionErrorCode; } + /** + * Text description of the error code returned by {@code getStagedSessionErrorCode}, or + * empty string if no error was encountered. + */ + public String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + /** {@hide} */ - public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) { + public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode, + String errorMessage) { mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; } @Override @@ -2099,6 +2118,7 @@ public class PackageInstaller { dest.writeBoolean(isSessionReady); dest.writeBoolean(isSessionFailed); dest.writeInt(mStagedSessionErrorCode); + dest.writeString(mStagedSessionErrorMessage); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b84567383bfb..783ee641d1f7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2060,6 +2060,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the + * device supports PIN, pattern and password as defined in Android CDD + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device includes an accelerometer. */ @SdkConstant(SdkConstantType.FEATURE) @@ -2215,6 +2223,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports attaching to IMS implementations using the ImsService API in telephony. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports connecting to USB devices * as the USB host. @@ -5764,7 +5779,7 @@ public abstract class PackageManager { */ @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional = true) - public abstract void setComponentEnabledSetting(ComponentName componentName, + public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName, @EnabledState int newState, @EnabledFlags int flags); /** @@ -5778,7 +5793,7 @@ public abstract class PackageManager { * @return Returns the current enabled state for the component. */ public abstract @EnabledState int getComponentEnabledSetting( - ComponentName componentName); + @NonNull ComponentName componentName); /** * Set the enabled setting for an application @@ -5793,7 +5808,7 @@ public abstract class PackageManager { */ @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional = true) - public abstract void setApplicationEnabledSetting(String packageName, + public abstract void setApplicationEnabledSetting(@NonNull String packageName, @EnabledState int newState, @EnabledFlags int flags); /** @@ -5807,7 +5822,7 @@ public abstract class PackageManager { * @return Returns the current enabled state for the application. * @throws IllegalArgumentException if the named package does not exist. */ - public abstract @EnabledState int getApplicationEnabledSetting(String packageName); + public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName); /** * Flush the package restrictions for a given user to disk. This forces the package restrictions diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index c7320b0d6ccf..c9a4c8270390 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -794,6 +794,12 @@ public abstract class PackageManagerInternal { "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS"; /** + * Extra field name for the set of installed users for a given rollback package. + */ + public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS = + "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS"; + + /** * Used as the {@code enableRollbackCode} argument for * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that * enabling rollback succeeded. @@ -827,4 +833,10 @@ public abstract class PackageManagerInternal { * Ask the package manager to compile layouts in the given package. */ public abstract boolean compileLayouts(String packageName); + + /* + * Inform the package manager that the pending package install identified by + * {@code token} can be completed. + */ + public abstract void finishPackageInstall(int token, boolean didLaunch); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 88a240f240cd..eb59cfc0fc4b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8512,7 +8512,9 @@ public class PackageParser { collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0); pi.packageName = apk.packageName; + ai.packageName = apk.packageName; pi.setLongVersionCode(apk.getLongVersionCode()); + ai.setVersionCode(apk.getLongVersionCode()); if (collectCerts) { if (apk.signingDetails.hasPastSigningCertificates()) { diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 6e519c1863e8..a8c3b889421b 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -475,14 +475,6 @@ public abstract class RegisteredServicesCache<V> { final List<ResolveInfo> resolveInfos = queryIntentServices(userId); for (ResolveInfo resolveInfo : resolveInfos) { try { - // if this package is not one of those changedUids, we don't need to scan it, - // since nothing in it changed, so save a call to parseServiceInfo, which - // can cause a large amount of the package apk to be loaded into memory. - // if this is the initial scan, changedUids will be null, and containsUid will - // trivially return true, and will call parseServiceInfo - if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) { - continue; - } ServiceInfo<V> info = parseServiceInfo(resolveInfo); if (info == null) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 4f7acd96aa6b..849fd03eacb3 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -635,4 +635,21 @@ public class ShortcutManager { } }; } + + /** + * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share + * target definitions in it's resources. + * + * @param packageName Package to check for share targets. + * @return True if the package has any share target definitions, False otherwise. + * @hide + */ + public boolean hasShareTargets(@NonNull String packageName) { + try { + return mService.hasShareTargets(mContext.getPackageName(), packageName, + injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl index 2faf3adb34b6..4f4c34b88cbd 100644 --- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl +++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl @@ -27,5 +27,4 @@ import android.os.RemoteCallback; */ oneway interface IRuntimePermissionPresenter { void getAppPermissions(String packageName, in RemoteCallback callback); - void revokeRuntimePermission(String packageName, String permissionName); } diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl index 7f557cd8bbe8..63d75a097d24 100644 --- a/core/java/android/content/rollback/IRollbackManager.aidl +++ b/core/java/android/content/rollback/IRollbackManager.aidl @@ -17,22 +17,24 @@ package android.content.rollback; import android.content.pm.ParceledListSlice; -import android.content.pm.StringParceledListSlice; import android.content.rollback.RollbackInfo; import android.content.IntentSender; /** {@hide} */ interface IRollbackManager { - RollbackInfo getAvailableRollback(String packageName); - - StringParceledListSlice getPackagesWithAvailableRollbacks(); - + ParceledListSlice getAvailableRollbacks(); ParceledListSlice getRecentlyExecutedRollbacks(); void executeRollback(in RollbackInfo rollback, String callerPackageName, in IntentSender statusReceiver); + // Exposed for use from the system server only. Callback from the package + // manager during the install flow when user data can be restored for a given + // package. + void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token); + // Exposed for test purposes only. void reloadPersistedData(); diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 204002426d17..4644a83de462 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -17,11 +17,10 @@ package android.content.rollback; import android.annotation.SystemApi; +import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcelable; -import java.util.Objects; - /** * Information about a rollback available for a particular package. * @@ -29,59 +28,41 @@ import java.util.Objects; */ @SystemApi public final class PackageRollbackInfo implements Parcelable { - /** - * The name of a package being rolled back. - */ - public final String packageName; + + private final VersionedPackage mVersionRolledBackFrom; + private final VersionedPackage mVersionRolledBackTo; /** - * The version the package was rolled back from. + * Returns the name of the package to roll back from. */ - public final PackageVersion higherVersion; + public String getPackageName() { + return mVersionRolledBackFrom.getPackageName(); + } /** - * The version the package was rolled back to. + * Returns the version of the package rolled back from. */ - public final PackageVersion lowerVersion; + public VersionedPackage getVersionRolledBackFrom() { + return mVersionRolledBackFrom; + } /** - * Represents a version of a package. + * Returns the version of the package rolled back to. */ - public static class PackageVersion { - public final long versionCode; - - // TODO(b/120200473): Include apk sha or some other way to distinguish - // between two different apks with the same version code. - public PackageVersion(long versionCode) { - this.versionCode = versionCode; - } - - @Override - public boolean equals(Object other) { - if (other instanceof PackageVersion) { - PackageVersion otherVersion = (PackageVersion) other; - return versionCode == otherVersion.versionCode; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(versionCode); - } + public VersionedPackage getVersionRolledBackTo() { + return mVersionRolledBackTo; } - public PackageRollbackInfo(String packageName, - PackageVersion higherVersion, PackageVersion lowerVersion) { - this.packageName = packageName; - this.higherVersion = higherVersion; - this.lowerVersion = lowerVersion; + /** @hide */ + public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, + VersionedPackage packageRolledBackTo) { + this.mVersionRolledBackFrom = packageRolledBackFrom; + this.mVersionRolledBackTo = packageRolledBackTo; } private PackageRollbackInfo(Parcel in) { - this.packageName = in.readString(); - this.higherVersion = new PackageVersion(in.readLong()); - this.lowerVersion = new PackageVersion(in.readLong()); + this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in); + this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in); } @Override @@ -91,9 +72,8 @@ public final class PackageRollbackInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeString(packageName); - out.writeLong(higherVersion.versionCode); - out.writeLong(lowerVersion.versionCode); + mVersionRolledBackFrom.writeToParcel(out, flags); + mVersionRolledBackTo.writeToParcel(out, flags); } public static final Parcelable.Creator<PackageRollbackInfo> CREATOR = diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java index 66df4fea3f23..8532b5a4844e 100644 --- a/core/java/android/content/rollback/RollbackInfo.java +++ b/core/java/android/content/rollback/RollbackInfo.java @@ -20,6 +20,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.List; + /** * Information about a set of packages that can be, or already have been * rolled back together. @@ -30,22 +32,38 @@ import android.os.Parcelable; public final class RollbackInfo implements Parcelable { /** - * The package that needs to be rolled back. + * A unique identifier for the rollback. */ - public final PackageRollbackInfo targetPackage; + private final int mRollbackId; + + private final List<PackageRollbackInfo> mPackages; - // TODO: Add a list of additional packages rolled back due to atomic - // install dependencies when rollback of atomic installs is supported. // TODO: Add a flag to indicate if reboot is required, when rollback of // staged installs is supported. /** @hide */ - public RollbackInfo(PackageRollbackInfo targetPackage) { - this.targetPackage = targetPackage; + public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) { + this.mRollbackId = rollbackId; + this.mPackages = packages; } private RollbackInfo(Parcel in) { - this.targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in); + mRollbackId = in.readInt(); + mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR); + } + + /** + * Returns a unique identifier for this rollback. + */ + public int getRollbackId() { + return mRollbackId; + } + + /** + * Returns the list of package that are rolled back. + */ + public List<PackageRollbackInfo> getPackages() { + return mPackages; } @Override @@ -55,7 +73,8 @@ public final class RollbackInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - targetPackage.writeToParcel(out, flags); + out.writeInt(mRollbackId); + out.writeTypedList(mPackages); } public static final Parcelable.Creator<RollbackInfo> CREATOR = diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java index c1c0bc1d3e07..2566ac562e11 100644 --- a/core/java/android/content/rollback/RollbackManager.java +++ b/core/java/android/content/rollback/RollbackManager.java @@ -17,7 +17,6 @@ package android.content.rollback; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -50,55 +49,26 @@ public final class RollbackManager { } /** - * Returns the rollback currently available to be executed for the given - * package. - * <p> - * The returned RollbackInfo describes what packages would be rolled back, - * including package version codes before and after rollback. The rollback - * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}. - * <p> - * TODO: What if there is no package installed on device for packageName? + * Returns a list of all currently available rollbacks. * - * @param packageName name of the package to get the availble RollbackInfo for. - * @return the rollback available for the package, or null if no rollback - * is available for the package. * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) { + public List<RollbackInfo> getAvailableRollbacks() { try { - return mBinder.getAvailableRollback(packageName); + return mBinder.getAvailableRollbacks().getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Gets the names of packages that are available for rollback. - * Call {@link #getAvailableRollback(String)} to get more information - * about the rollback available for a particular package. - * - * @return the names of packages that are available for rollback. - * @throws SecurityException if the caller does not have the - * MANAGE_ROLLBACKS permission. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @NonNull List<String> getPackagesWithAvailableRollbacks() { - try { - return mBinder.getPackagesWithAvailableRollbacks().getList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - - /** - * Gets the list of all recently executed rollbacks. + * Gets the list of all recently committed rollbacks. * This is for the purposes of preventing re-install of a bad version of a - * package. + * package and monitoring the status of a staged rollback. * <p> - * Returns an empty list if there are no recently executed rollbacks. + * Returns an empty list if there are no recently committed rollbacks. * <p> * To avoid having to keep around complete rollback history forever on a * device, the returned list of rollbacks is only guaranteed to include @@ -107,12 +77,12 @@ public final class RollbackManager { * (without the possibility of rollback) to a higher version code than was * rolled back from. * - * @return the recently executed rollbacks + * @return the recently committed rollbacks * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() { + public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() { try { return mBinder.getRecentlyExecutedRollbacks().getList(); } catch (RemoteException e) { @@ -121,25 +91,24 @@ public final class RollbackManager { } /** - * Execute the given rollback, rolling back all versions of the packages - * to the last good versions previously installed on the device as - * specified in the given rollback object. The rollback will fail if any - * of the installed packages or available rollbacks are inconsistent with - * the versions specified in the given rollback object, which can happen - * if a package has been updated or a rollback expired since the rollback - * object was retrieved from {@link #getAvailableRollback(String)}. + * Commit the rollback with given id, rolling back all versions of the + * packages to the last good versions previously installed on the device + * as specified in the corresponding RollbackInfo object. The + * rollback will fail if any of the installed packages or available + * rollbacks are inconsistent with the versions specified in the given + * rollback object, which can happen if a package has been updated or a + * rollback expired since the rollback object was retrieved from + * {@link #getAvailableRollbacks()}. * <p> * TODO: Specify the returns status codes. - * TODO: What happens in case reboot is required for the rollback to take - * effect for staged installs? * - * @param rollback to execute + * @param rollback to commit * @param statusReceiver where to deliver the results * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public void executeRollback(@NonNull RollbackInfo rollback, + public void commitRollback(@NonNull RollbackInfo rollback, @NonNull IntentSender statusReceiver) { try { mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver); diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 9d37d9939127..209afb88ccd3 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -252,12 +252,55 @@ public interface BiometricFaceConstants { public static final int FACE_ACQUIRED_TOO_SIMILAR = 15; /** + * The magnitude of the pan angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The pan angle is defined as the angle swept out by the user’s face turning + * their neck left and right. The pan angle would be zero if the user faced the + * camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; + + /** + * The magnitude of the tilt angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The tilt angle is defined as the angle swept out by the user’s face looking up + * and down. The pan angle would be zero if the user faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; + + /** + * The magnitude of the roll angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The roll angle is defined as the angle swept out by the user’s face tilting their head + * towards their shoulders to the left and right. The pan angle would be zero if the user + * faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; + + /** + * The user’s face has been obscured by some object. + * + * The user should be informed to remove any objects from the line of sight from + * the sensor to the user’s face. + */ + public static final int FACE_ACQUIRED_FACE_OBSCURED = 19; + + /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. * * @hide */ - public static final int FACE_ACQUIRED_VENDOR = 16; + public static final int FACE_ACQUIRED_VENDOR = 20; /** * @hide diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8aac1bfa1f8d..5afe1a9309f4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -171,5 +171,39 @@ public class BiometricManager { Slog.w(TAG, "resetTimeout(): Service not connected"); } } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialSuccess() { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialSuccess(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected"); + } + } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialError(int error, String message) { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialError(error, message); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); + } + } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c69b68e4360a..ec62abaab6a2 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -77,6 +77,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; + /** + * @hide + */ + public static final String KEY_ENABLE_FALLBACK = "enable_fallback"; /** * Error/help message will show for this amount of time. @@ -242,6 +246,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * The user will first be prompted to authenticate with biometrics, but also given the + * option to authenticate with their device PIN, pattern, or password. + * @param enable When true, the prompt will fall back to ask for the user's device + * credentials (PIN, pattern, or password). + * @return + */ + public Builder setEnableFallback(boolean enable) { + mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. @@ -250,11 +266,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); + final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK); if (TextUtils.isEmpty(title) && !useDefaultTitle) { throw new IllegalArgumentException("Title must be set and non-empty"); - } else if (TextUtils.isEmpty(negative)) { + } else if (TextUtils.isEmpty(negative) && !enableFallback) { throw new IllegalArgumentException("Negative text must be set and non-empty"); + } else if (!TextUtils.isEmpty(negative) && enableFallback) { + throw new IllegalArgumentException("Can't have both negative button behavior" + + " and fallback enabled"); } return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); } @@ -514,6 +534,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } + if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) { + throw new IllegalArgumentException("Fallback not supported with crypto"); + } authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index de828f2d6757..e4336d171ab6 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -51,4 +51,12 @@ interface IBiometricService { // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) void resetTimeout(in byte [] token); + + // TODO(b/123378871): Remove when moved. + // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's + // setEnableFallback method, since there's no way for us to intercept onActivityResult. + // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult. + void onConfirmDeviceCredentialSuccess(); + // TODO(b/123378871): Remove when moved. + void onConfirmDeviceCredentialError(int error, String message); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 105ae6815589..97583089736e 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3722,12 +3722,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>String containing the ids of the underlying physical cameras.</p> - * <p>For a logical camera, this is concatenation of all underlying physical camera ids. - * The null terminator for physical camera id must be preserved so that the whole string - * can be tokenized using '\0' to generate list of physical camera ids.</p> - * <p>For example, if the physical camera ids of the logical camera are "2" and "3", the + * <p>For a logical camera, this is concatenation of all underlying physical camera IDs. + * The null terminator for physical camera ID must be preserved so that the whole string + * can be tokenized using '\0' to generate list of physical camera IDs.</p> + * <p>For example, if the physical camera IDs of the logical camera are "2" and "3", the * value of this tag will be ['2', '\0', '3', '\0'].</p> - * <p>The number of physical camera ids must be no less than 2.</p> + * <p>The number of physical camera IDs must be no less than 2.</p> * <p><b>Units</b>: UTF-8 null-terminated string</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 402472af65a3..6302aa536214 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -428,6 +428,10 @@ public abstract class CameraMetadata<TKey> { * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will * additionally return a min frame duration that is greater than * zero for each supported size-format combination.</p> + * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when the underlying active + * physical camera switches, exposureTime, sensitivity, and lens properties may change + * even if AE/AF is locked. However, the overall auto exposure and auto focus experience + * for users will be consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p> * * @see CaptureRequest#BLACK_LEVEL_LOCK * @see CaptureRequest#CONTROL_AE_LOCK @@ -485,6 +489,10 @@ public abstract class CameraMetadata<TKey> { * will accurately report the values applied by AWB in the result.</p> * <p>A given camera device may also support additional post-processing * controls, but this capability only covers the above list of controls.</p> + * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when underlying active + * physical camera switches, tonemap, white balance, and shading map may change even if + * awb is locked. However, the overall post-processing experience for users will be + * consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p> * * @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES @@ -847,7 +855,7 @@ public abstract class CameraMetadata<TKey> { * </li> * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be * the same.</li> - * <li>The logical camera device must be LIMITED or higher device.</li> + * <li>The logical camera must be LIMITED or higher device.</li> * </ul> * <p>Both the logical camera device and its underlying physical devices support the * mandatory stream combinations required for their device levels.</p> @@ -867,13 +875,84 @@ public abstract class CameraMetadata<TKey> { * <p>Using physical streams in place of a logical stream of the same size and format will * not slow down the frame rate of the capture, as long as the minimum frame duration * of the physical and logical streams are the same.</p> + * <p>A logical camera device's dynamic metadata may contain + * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} to notify the application of the current + * active physical camera Id. An active physical camera is the physical camera from which + * the logical camera's main image data outputs (YUV or RAW) and metadata come from. + * In addition, this serves as an indication which physical camera is used to output to + * a RAW stream, or in case only physical cameras support RAW, which physical RAW stream + * the application should request.</p> + * <p>Logical camera's static metadata tags below describe the default active physical + * camera. An active physical camera is default if it's used when application directly + * uses requests built from a template. All templates will default to the same active + * physical camera.</p> + * <ul> + * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied}</li> + * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}</li> + * <li>{@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}</li> + * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}</li> + * <li>{@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}</li> + * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}</li> + * <li>{@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2}</li> + * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1}</li> + * <li>{@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2}</li> + * <li>{@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}</li> + * <li>{@link CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY android.sensor.maxAnalogSensitivity}</li> + * <li>{@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}</li> + * <li>{@link CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES android.sensor.availableTestPatternModes}</li> + * <li>{@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}</li> + * <li>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}</li> + * <li>{@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration}</li> + * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> + * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> + * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> + * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li> + * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li> + * </ul> + * <p>To maintain backward compatibility, the capture request and result metadata tags + * required for basic camera functionalities will be solely based on the + * logical camera capabiltity. Other request and result metadata tags, on the other + * hand, will be based on current active physical camera. For example, the physical + * cameras' sensor sensitivity and lens capability could be different from each other. + * So when the application manually controls sensor exposure time/gain, or does manual + * focus control, it must checks the current active physical camera's exposure, gain, + * and focus distance range.</p> * * @see CameraCharacteristics#LENS_DISTORTION + * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION + * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE + * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION * @see CameraCharacteristics#LENS_POSE_REFERENCE * @see CameraCharacteristics#LENS_POSE_ROTATION * @see CameraCharacteristics#LENS_POSE_TRANSLATION + * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + * @see CameraCharacteristics#SENSOR_AVAILABLE_TEST_PATTERN_MODES + * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN + * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 + * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 + * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 + * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 + * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX1 + * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX2 + * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT + * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE + * @see CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED + * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION + * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE + * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE + * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL + * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY + * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 + * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 8ebaf2f7db44..5f05cfb459bf 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2146,7 +2146,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>32 characters describing GPS algorithm to * include in EXIF.</p> - * <p><b>Units</b>: UTF-8 null-terminated string</p> * <p>This key is available on all devices.</p> * @hide */ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 3d70c516b577..585c597c58e4 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2470,7 +2470,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>32 characters describing GPS algorithm to * include in EXIF.</p> - * <p><b>Units</b>: UTF-8 null-terminated string</p> * <p>This key is available on all devices.</p> * @hide */ @@ -4638,6 +4637,23 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Float>("android.reprocess.effectiveExposureFactor", float.class); /** + * <p>String containing the ID of the underlying active physical camera.</p> + * <p>The ID of the active physical camera that's backing the logical camera. All camera + * streams and metadata that are not physical camera specific will be originating from this + * physical camera. This must be one of valid physical IDs advertised in the physicalIds + * static tag.</p> + * <p>For a logical camera made up of physical cameras where each camera's lenses have + * different characteristics, the camera device may choose to switch between the physical + * cameras when application changes FOCAL_LENGTH or SCALER_CROP_REGION. + * At the time of lens switch, this result metadata reflects the new active physical camera + * ID.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID = + new Key<String>("android.logicalMultiCamera.activePhysicalId", String.class); + + /** * <p>Mode of operation for the lens distortion correction block.</p> * <p>The lens distortion correction block attempts to improve image quality by fixing * radial, tangential, or other geometric aberrations in the camera device's optics. If diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index fa335c8b9924..27f0b0425ec3 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -23,16 +23,22 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.metrics.LogMaker; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.provider.Settings.Secure; import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.LocalTime; /** * Manages the display's color transforms and modes. @@ -81,7 +87,82 @@ public final class ColorDisplayManager { @SystemApi public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4; + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM_TIME, AUTO_MODE_TWILIGHT }) + public @interface AutoMode {} + + /** + * Auto mode value to prevent Night display from being automatically activated. It can still + * be activated manually via {@link #setNightDisplayActivated(boolean)}. + * + * @see #setNightDisplayAutoMode(int) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_DISABLED = 0; + /** + * Auto mode value to automatically activate Night display at a specific start and end time. + * + * @see #setNightDisplayAutoMode(int) + * @see #setNightDisplayCustomStartTime(LocalTime) + * @see #setNightDisplayCustomEndTime(LocalTime) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_CUSTOM_TIME = 1; + /** + * Auto mode value to automatically activate Night display from sunset to sunrise. + * + * @see #setNightDisplayAutoMode(int) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_TWILIGHT = 2; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC}) + public @interface ColorMode {} + + /** + * Color mode with natural colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_NATURAL = 0; + /** + * Color mode with boosted colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_BOOSTED = 1; + /** + * Color mode with saturated colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_SATURATED = 2; + /** + * Color mode with automatic colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_AUTOMATIC = 3; + private final ColorDisplayManagerInternal mManager; + private MetricsLogger mMetricsLogger; /** * @hide @@ -91,6 +172,176 @@ public final class ColorDisplayManager { } /** + * (De)activates the night display transform. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayActivated(boolean activated) { + return mManager.setNightDisplayActivated(activated); + } + + /** + * Returns whether the night display transform is currently active. + * + * @hide + */ + public boolean isNightDisplayActivated() { + return mManager.isNightDisplayActivated(); + } + + /** + * Sets the color temperature of the night display transform. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayColorTemperature(int temperature) { + return mManager.setNightDisplayColorTemperature(temperature); + } + + /** + * Gets the color temperature of the night display transform. + * + * @hide + */ + public int getNightDisplayColorTemperature() { + return mManager.getNightDisplayColorTemperature(); + } + + /** + * Returns the current auto mode value controlling when Night display will be automatically + * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or + * {@link #AUTO_MODE_TWILIGHT}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public @AutoMode int getNightDisplayAutoMode() { + return mManager.getNightDisplayAutoMode(); + } + + /** + * Returns the current auto mode value, without validation, or {@code 1} if the auto mode has + * never been set. + * + * @hide + */ + public int getNightDisplayAutoModeRaw() { + return mManager.getNightDisplayAutoModeRaw(); + } + + /** + * Sets the current auto mode value controlling when Night display will be automatically + * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or + * {@link #AUTO_MODE_TWILIGHT}. + * + * @param autoMode the new auto mode to use + * @return {@code true} if new auto mode was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayAutoMode(@AutoMode int autoMode) { + if (autoMode != AUTO_MODE_DISABLED + && autoMode != AUTO_MODE_CUSTOM_TIME + && autoMode != AUTO_MODE_TWILIGHT) { + throw new IllegalArgumentException("Invalid autoMode: " + autoMode); + } + if (mManager.getNightDisplayAutoMode() != autoMode) { + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(autoMode)); + } + return mManager.setNightDisplayAutoMode(autoMode); + } + + /** + * Returns the local time when Night display will be automatically activated when using + * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. + * + * @hide + */ + public @NonNull LocalTime getNightDisplayCustomStartTime() { + return mManager.getNightDisplayCustomStartTime().getLocalTime(); + } + + /** + * Sets the local time when Night display will be automatically activated when using + * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. + * + * @param startTime the local time to automatically activate Night display + * @return {@code true} if the new custom start time was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayCustomStartTime(@NonNull LocalTime startTime) { + if (startTime == null) { + throw new IllegalArgumentException("startTime cannot be null"); + } + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(0)); + return mManager.setNightDisplayCustomStartTime(new Time(startTime)); + } + + /** + * Returns the local time when Night display will be automatically deactivated when using + * {@link #AUTO_MODE_CUSTOM_TIME}. + * + * @hide + */ + public @NonNull LocalTime getNightDisplayCustomEndTime() { + return mManager.getNightDisplayCustomEndTime().getLocalTime(); + } + + /** + * Sets the local time when Night display will be automatically deactivated when using + * {@link #AUTO_MODE_CUSTOM_TIME}. + * + * @param endTime the local time to automatically deactivate Night display + * @return {@code true} if the new custom end time was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayCustomEndTime(@NonNull LocalTime endTime) { + if (endTime == null) { + throw new IllegalArgumentException("endTime cannot be null"); + } + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(1)); + return mManager.setNightDisplayCustomEndTime(new Time(endTime)); + } + + /** + * Sets the current display color mode. + * + * @hide + */ + public void setColorMode(int colorMode) { + mManager.setColorMode(colorMode); + } + + /** + * Gets the current display color mode. + * + * @hide + */ + public int getColorMode() { + return mManager.getColorMode(); + } + + /** * Returns whether the device has a wide color gamut display. * * @hide @@ -138,6 +389,28 @@ public final class ColorDisplayManager { } /** + * Returns the minimum allowed color temperature (in Kelvin) to tint the display when + * activated. + * + * @hide + */ + public static int getMinimumColorTemperature(Context context) { + return context.getResources() + .getInteger(R.integer.config_nightDisplayColorTemperatureMin); + } + + /** + * Returns the maximum allowed color temperature (in Kelvin) to tint the display when + * activated. + * + * @hide + */ + public static int getMaximumColorTemperature(Context context) { + return context.getResources() + .getInteger(R.integer.config_nightDisplayColorTemperatureMax); + } + + /** * Returns {@code true} if display white balance is supported by the device. * * @hide @@ -167,6 +440,25 @@ public final class ColorDisplayManager { return mManager.getTransformCapabilities(); } + /** + * Returns whether accessibility transforms are currently enabled, which determines whether + * color modes are currently configurable for this device. + * + * @hide + */ + public static boolean areAccessibilityTransformsEnabled(Context context) { + final ContentResolver cr = context.getContentResolver(); + return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1 + || Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) == 1; + } + + private MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } + private static class ColorDisplayManagerInternal { private static ColorDisplayManagerInternal sInstance; @@ -192,6 +484,94 @@ public final class ColorDisplayManager { } } + boolean isNightDisplayActivated() { + try { + return mCdm.isNightDisplayActivated(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayActivated(boolean activated) { + try { + return mCdm.setNightDisplayActivated(activated); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayColorTemperature() { + try { + return mCdm.getNightDisplayColorTemperature(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayColorTemperature(int temperature) { + try { + return mCdm.setNightDisplayColorTemperature(temperature); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayAutoMode() { + try { + return mCdm.getNightDisplayAutoMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayAutoModeRaw() { + try { + return mCdm.getNightDisplayAutoModeRaw(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayAutoMode(int autoMode) { + try { + return mCdm.setNightDisplayAutoMode(autoMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + Time getNightDisplayCustomStartTime() { + try { + return mCdm.getNightDisplayCustomStartTime(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayCustomStartTime(Time startTime) { + try { + return mCdm.setNightDisplayCustomStartTime(startTime); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + Time getNightDisplayCustomEndTime() { + try { + return mCdm.getNightDisplayCustomEndTime(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayCustomEndTime(Time endTime) { + try { + return mCdm.setNightDisplayCustomEndTime(endTime); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + boolean isDeviceColorManaged() { try { return mCdm.isDeviceColorManaged(); @@ -216,6 +596,22 @@ public final class ColorDisplayManager { } } + int getColorMode() { + try { + return mCdm.getColorMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + void setColorMode(int colorMode) { + try { + mCdm.setColorMode(colorMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + int getTransformCapabilities() { try { return mCdm.getTransformCapabilities(); diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 7e45441c804a..f3ebd7f36fd6 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -20,6 +20,7 @@ import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ParceledListSlice; import android.content.res.Resources; +import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.display.DisplayManager.DisplayListener; import android.media.projection.IMediaProjection; @@ -80,12 +81,20 @@ public final class DisplayManagerGlobal { new ArrayList<DisplayListenerDelegate>(); private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); + private final ColorSpace mWideColorSpace; private int[] mDisplayIdCache; private int mWifiDisplayScanNestCount; private DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; + try { + mWideColorSpace = + ColorSpace.get( + ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** @@ -495,6 +504,17 @@ public final class DisplayManagerGlobal { } /** + * Gets the preferred wide gamut color space for all displays. + * The wide gamut color space is returned from composition pipeline + * based on hardware capability. + * + * @hide + */ + public ColorSpace getPreferredWideGamutColorSpace() { + return mWideColorSpace; + } + + /** * Sets the global brightness configuration for a given user. * * @hide diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 53cb8db8cc3d..30e76cfe2787 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -16,6 +16,8 @@ package android.hardware.display; +import android.hardware.display.Time; + /** @hide */ interface IColorDisplayManager { boolean isDeviceColorManaged(); @@ -24,4 +26,19 @@ interface IColorDisplayManager { boolean setAppSaturationLevel(String packageName, int saturationLevel); int getTransformCapabilities(); + + boolean isNightDisplayActivated(); + boolean setNightDisplayActivated(boolean activated); + int getNightDisplayColorTemperature(); + boolean setNightDisplayColorTemperature(int temperature); + int getNightDisplayAutoMode(); + int getNightDisplayAutoModeRaw(); + boolean setNightDisplayAutoMode(int autoMode); + Time getNightDisplayCustomStartTime(); + boolean setNightDisplayCustomStartTime(in Time time); + Time getNightDisplayCustomEndTime(); + boolean setNightDisplayCustomEndTime(in Time time); + + int getColorMode(); + void setColorMode(int colorMode); }
\ No newline at end of file diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index aae8afbcad49..5ea8bd67ce75 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -116,4 +116,9 @@ interface IDisplayManager { // Get the minimum brightness curve. Curve getMinimumBrightnessCurve(); + + // Gets the id of the preferred wide gamut color space for all displays. + // The wide gamut color space is returned from composition pipeline + // based on hardware capability. + int getPreferredWideGamutColorSpaceId(); } diff --git a/core/java/android/hardware/display/Time.aidl b/core/java/android/hardware/display/Time.aidl new file mode 100644 index 000000000000..95cb5631e72f --- /dev/null +++ b/core/java/android/hardware/display/Time.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable Time;
\ No newline at end of file diff --git a/core/java/android/hardware/display/Time.java b/core/java/android/hardware/display/Time.java new file mode 100644 index 000000000000..b943ac65ab06 --- /dev/null +++ b/core/java/android/hardware/display/Time.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.time.LocalTime; + +/** + * @hide + */ +public final class Time implements Parcelable { + + private final int mHour; + private final int mMinute; + private final int mSecond; + private final int mNano; + + public Time(LocalTime localTime) { + mHour = localTime.getHour(); + mMinute = localTime.getMinute(); + mSecond = localTime.getSecond(); + mNano = localTime.getNano(); + } + + public Time(Parcel parcel) { + mHour = parcel.readInt(); + mMinute = parcel.readInt(); + mSecond = parcel.readInt(); + mNano = parcel.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int parcelableFlags) { + parcel.writeInt(mHour); + parcel.writeInt(mMinute); + parcel.writeInt(mSecond); + parcel.writeInt(mNano); + } + + public LocalTime getLocalTime() { + return LocalTime.of(mHour, mMinute, mSecond, mNano); + } + + public static final Parcelable.Creator<Time> CREATOR = new Parcelable.Creator<Time>() { + + @Override + public Time createFromParcel(Parcel source) { + return new Time(source); + } + + @Override + public Time[] newArray(int size) { + return new Time[size]; + } + }; +} diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index a98b31ad6a5e..56020b24c556 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -33,6 +34,8 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.List; /** @@ -106,9 +109,24 @@ public final class HdmiControlManager { public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; + @IntDef ({ + RESULT_SUCCESS, + RESULT_TIMEOUT, + RESULT_SOURCE_NOT_AVAILABLE, + RESULT_TARGET_NOT_AVAILABLE, + RESULT_ALREADY_IN_PROGRESS, + RESULT_EXCEPTION, + RESULT_INCORRECT_MODE, + RESULT_COMMUNICATION_FAILED, + }) + public @interface ControlCallbackResult {} + + /** Control operation is successfully handled by the framework. */ public static final int RESULT_SUCCESS = 0; public static final int RESULT_TIMEOUT = 1; + /** Source device that the application is using is not available. */ public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; + /** Target device that the application is controlling is not available. */ public static final int RESULT_TARGET_NOT_AVAILABLE = 3; @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; @@ -394,16 +412,15 @@ public final class HdmiControlManager { /** * Gets an object that represents an HDMI-CEC logical device of type switch on the system. * - * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also - * possible to communicate with other logical devices hosted in the same system if the system is - * configured to host more than one type of HDMI-CEC logical devices. + * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus. + * It is also possible to communicate with other logical devices hosted in the same + * system if the system is configured to host more than one type of HDMI-CEC logical device. * * @return {@link HdmiSwitchClient} instance. {@code null} on failure. - * - * TODO(b/110094868): unhide for Q * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiSwitchClient getSwitchClient() { return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); @@ -412,11 +429,15 @@ public final class HdmiControlManager { /** * Get a snapshot of the real-time status of the remote devices. * - * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device. + * <p>This only applies to devices with multiple HDMI inputs. + * + * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty + * list will be returned if there is none. * - * TODO(b/110094868): unhide for Q * @hide */ + @SystemApi + @Nullable public List<HdmiDeviceInfo> getConnectedDevicesList() { try { return mService.getDeviceList(); @@ -426,14 +447,17 @@ public final class HdmiControlManager { } /** - * Power off the target device. + * Power off the target device by sending CEC commands. * - * @param deviceInfo HdmiDeviceInfo of the device to be powered off + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. + * + * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off. * - * TODO(b/110094868): unhide for Q * @hide */ + @SystemApi public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.powerOffRemoteDevice( deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); @@ -443,14 +467,16 @@ public final class HdmiControlManager { } /** - * Power on the target device. + * Power on the target device by sending CEC commands. * - * @param deviceInfo HdmiDeviceInfo of the device to be powered on + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. + * + * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on. * - * TODO(b/110094868): unhide for Q * @hide */ public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.powerOnRemoteDevice( deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); @@ -460,14 +486,17 @@ public final class HdmiControlManager { } /** - * Ask the target device to be the new Active Source. + * Request the target device to be the new Active Source by sending CEC commands. + * + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. * * @param deviceInfo HdmiDeviceInfo of the target device * - * TODO(b/110094868): unhide for Q * @hide */ - public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) { + @SystemApi + public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress()); } catch (RemoteException e) { @@ -506,8 +535,13 @@ public final class HdmiControlManager { /** * Get the physical address of the device. * + * <p>Physical address needs to be automatically adjusted when devices are phyiscally or + * electrically added or removed from the device tree. Please see HDMI Specification Version + * 1.4b 8.7 Physical Address for more details on the address discovery proccess. + * * @hide */ + @SystemApi public int getPhysicalAddress() { if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) { return mPhysicalAddress; @@ -521,16 +555,19 @@ public final class HdmiControlManager { } /** - * Check if the target device is connected to the current device. The - * API also returns true if the current device is the target. + * Check if the target remote device is connected to the current device. + * + * <p>The API also returns true if the current device is the target. * * @param targetDevice {@link HdmiDeviceInfo} of the target device. - * @return true if {@code device} is directly or indirectly connected to the + * @return true if {@code targetDevice} is directly or indirectly + * connected to the current device. * - * TODO(b/110094868): unhide for Q * @hide */ - public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) { + @SystemApi + public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) { + Preconditions.checkNotNull(targetDevice); mPhysicalAddress = getPhysicalAddress(); if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { return false; diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java index 1ac29736f964..a0365129a89a 100644 --- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -15,24 +15,30 @@ */ package android.hardware.hdmi; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult; +import android.os.Binder; import android.os.RemoteException; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** - * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which - * acts as switch. + * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device. * - * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, - * but it is used by all Android TV devices that have multiple HDMI inputs, - * even if it is not a "pure" swicth and has another device type like TV or Player. + * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is + * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth + * and has another device type like TV or Player. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ +@SystemApi public class HdmiSwitchClient extends HdmiClient { private static final String TAG = "HdmiSwitchClient"; @@ -41,17 +47,15 @@ public class HdmiSwitchClient extends HdmiClient { super(service); } - private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { + private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) { return new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { - callback.onComplete(result); + listener.onSelect(result); } }; } - /** @hide */ - // TODO(b/110094868): unhide for Q @Override public int getDeviceType() { return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; @@ -61,20 +65,17 @@ public class HdmiSwitchClient extends HdmiClient { * Selects a CEC logical device to be a new active source. * * @param logicalAddress logical address of the device to select - * @param callback callback to get the result with - * @throws {@link IllegalArgumentException} if the {@code callback} is null + * @param listener listener to get the result with * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback must not be null."); - } + public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); try { - mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); + mService.deviceSelect(logicalAddress, getCallbackWrapper(listener)); } catch (RemoteException e) { Log.e(TAG, "failed to select device: ", e); + throw e.rethrowFromSystemServer(); } } @@ -82,20 +83,83 @@ public class HdmiSwitchClient extends HdmiClient { * Selects a HDMI port to be a new route path. * * @param portId HDMI port to select - * @param callback callback to get the result with - * @throws {@link IllegalArgumentException} if the {@code callback} is null + * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()} + * to get portId of a specific TV Input. + * @param listener listener to get the result with * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public void portSelect(int portId, @NonNull SelectCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("Callback must not be null"); + @SystemApi + public void selectPort(int portId, @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); + try { + mService.portSelect(portId, getCallbackWrapper(listener)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select port: ", e); + throw e.rethrowFromSystemServer(); } + } + + /** + * Selects a CEC logical device to be a new active source. + * + * @param logicalAddress logical address of the device to select + * @param executor executor to allow the develper to specify the thread upon which the listeners + * will be invoked + * @param listener listener to get the result with + * + * @hide + */ + public void selectDevice( + int logicalAddress, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); + try { + mService.deviceSelect(logicalAddress, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + Binder.withCleanCallingIdentity( + () -> executor.execute(() -> listener.onSelect(result))); + } + } + ); + } catch (RemoteException e) { + Log.e(TAG, "failed to select device: ", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Selects a HDMI port to be a new route path. + * + * @param portId HDMI port to select + * @param executor executor to allow the develper to specify the thread upon which the listeners + * will be invoked + * @param listener listener to get the result with + * + * @hide + */ + @SystemApi + public void selectPort( + int portId, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); try { - mService.portSelect(portId, getCallbackWrapper(callback)); + mService.portSelect(portId, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + Binder.withCleanCallingIdentity( + () -> executor.execute(() -> listener.onSelect(result))); + } + } + ); } catch (RemoteException e) { Log.e(TAG, "failed to select port: ", e); + throw e.rethrowFromSystemServer(); } } @@ -105,10 +169,9 @@ public class HdmiSwitchClient extends HdmiClient { * <p>This only applies to device with multiple HDMI inputs * * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if - * there is none. + * there is none. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ public List<HdmiDeviceInfo> getDeviceList() { try { @@ -120,18 +183,19 @@ public class HdmiSwitchClient extends HdmiClient { } /** - * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. + * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public interface SelectCallback { + @SystemApi + public interface OnSelectListener { /** * Called when the operation is finished. * - * @param result the result value of {@link #deviceSelect} or {@link #portSelect}. + * @param result callback result. + * @see {@link ControlCallbackResult} */ - void onComplete(int result); + void onSelect(@ControlCallbackResult int result); } } diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index edc3f9466efc..299a00a426d4 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -120,6 +120,9 @@ interface IUsbManager /* Sets the port's current role. */ void setPortRoles(in String portId, int powerRole, int dataRole); + /* Enable/disable contaminant detection */ + void enableContaminantDetection(in String portId, boolean enable); + /* Sets USB device connection handler. */ void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); } diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java index 7f7ba96b40f2..30388afc0d13 100644 --- a/core/java/android/hardware/usb/ParcelableUsbPort.java +++ b/core/java/android/hardware/usb/ParcelableUsbPort.java @@ -31,10 +31,21 @@ import com.android.internal.annotations.Immutable; public final class ParcelableUsbPort implements Parcelable { private final @NonNull String mId; private final int mSupportedModes; + private final int mSupportedContaminantProtectionModes; + private final boolean mSupportsEnableContaminantPresenceProtection; + private final boolean mSupportsEnableContaminantPresenceDetection; - private ParcelableUsbPort(@NonNull String id, int supportedModes) { + private ParcelableUsbPort(@NonNull String id, int supportedModes, + int supportedContaminantProtectionModes, + boolean supportsEnableContaminantPresenceProtection, + boolean supportsEnableContaminantPresenceDetection) { mId = id; mSupportedModes = supportedModes; + mSupportedContaminantProtectionModes = supportedContaminantProtectionModes; + mSupportsEnableContaminantPresenceProtection = + supportsEnableContaminantPresenceProtection; + mSupportsEnableContaminantPresenceDetection = + supportsEnableContaminantPresenceDetection; } /** @@ -45,7 +56,10 @@ public final class ParcelableUsbPort implements Parcelable { * @return The parcelable version of the port */ public static @NonNull ParcelableUsbPort of(@NonNull UsbPort port) { - return new ParcelableUsbPort(port.getId(), port.getSupportedModes()); + return new ParcelableUsbPort(port.getId(), port.getSupportedModes(), + port.getSupportedContaminantProtectionModes(), + port.supportsEnableContaminantPresenceProtection(), + port.supportsEnableContaminantPresenceDetection()); } /** @@ -56,7 +70,9 @@ public final class ParcelableUsbPort implements Parcelable { * @return The UsbPort for this object */ public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) { - return new UsbPort(usbManager, mId, mSupportedModes); + return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes, + mSupportsEnableContaminantPresenceProtection, + mSupportsEnableContaminantPresenceDetection); } @Override @@ -68,6 +84,9 @@ public final class ParcelableUsbPort implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mId); dest.writeInt(mSupportedModes); + dest.writeInt(mSupportedContaminantProtectionModes); + dest.writeBoolean(mSupportsEnableContaminantPresenceProtection); + dest.writeBoolean(mSupportsEnableContaminantPresenceDetection); } public static final Creator<ParcelableUsbPort> CREATOR = @@ -76,7 +95,14 @@ public final class ParcelableUsbPort implements Parcelable { public ParcelableUsbPort createFromParcel(Parcel in) { String id = in.readString(); int supportedModes = in.readInt(); - return new ParcelableUsbPort(id, supportedModes); + int supportedContaminantProtectionModes = in.readInt(); + boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); + boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); + + return new ParcelableUsbPort(id, supportedModes, + supportedContaminantProtectionModes, + supportsEnableContaminantPresenceProtection, + supportsEnableContaminantPresenceDetection); } @Override diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 601447814952..eb148b94ac7b 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -854,6 +854,20 @@ public class UsbManager { } /** + * Enables USB port contaminant detection algorithm. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + void enableContaminantDetection(@NonNull UsbPort port, boolean enable) { + try { + mService.enableContaminantDetection(port.getId(), enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the component that will handle USB device connection. * <p> * Setting component allows to specify external USB host manager to handle use cases, where diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index 37154e4c81b2..c674480c2a75 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,6 +16,10 @@ package android.hardware.usb; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE; @@ -48,16 +52,21 @@ public final class UsbPort { private final String mId; private final int mSupportedModes; private final UsbManager mUsbManager; + private final int mSupportedContaminantProtectionModes; + private final boolean mSupportsEnableContaminantPresenceProtection; + private final boolean mSupportsEnableContaminantPresenceDetection; private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES; - /** * Points to the first power role in the IUsb HAL. */ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; /** @hide */ - public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes) { + public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes, + int supportedContaminantProtectionModes, + boolean supportsEnableContaminantPresenceProtection, + boolean supportsEnableContaminantPresenceDetection) { Preconditions.checkNotNull(id); Preconditions.checkFlagsArgument(supportedModes, MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY); @@ -65,6 +74,11 @@ public final class UsbPort { mUsbManager = usbManager; mId = id; mSupportedModes = supportedModes; + mSupportedContaminantProtectionModes = supportedContaminantProtectionModes; + mSupportsEnableContaminantPresenceProtection = + supportsEnableContaminantPresenceProtection; + mSupportsEnableContaminantPresenceDetection = + supportsEnableContaminantPresenceDetection; } /** @@ -93,6 +107,36 @@ public final class UsbPort { return mSupportedModes; } + /** + * Gets the supported port proctection modes when the port is contaminated. + * <p> + * The actual mode of the port is decided by the hardware + * </p> + * + * @hide + */ + public int getSupportedContaminantProtectionModes() { + return mSupportedContaminantProtectionModes; + } + + /** + * Tells if UsbService can enable/disable contaminant presence protection. + * + * @hide + */ + public boolean supportsEnableContaminantPresenceProtection() { + return mSupportsEnableContaminantPresenceProtection; + } + + /** + * Tells if UsbService can enable/disable contaminant presence detection. + * + * @hide + */ + public boolean supportsEnableContaminantPresenceDetection() { + return mSupportsEnableContaminantPresenceDetection; + } + /** * Gets the status of this USB port. * @@ -131,6 +175,12 @@ public final class UsbPort { } /** + * @hide + **/ + public void enableContaminantDetection(boolean enable) { + mUsbManager.enableContaminantDetection(this, enable); + } + /** * Combines one power and one data role together into a unique value with * exactly one bit set. This can be used to efficiently determine whether * a combination of roles is supported by testing whether that bit is present @@ -206,6 +256,22 @@ public final class UsbPort { } /** @hide */ + public static String contaminantPresenceStatusToString(int contaminantPresenceStatus) { + switch (contaminantPresenceStatus) { + case CONTAMINANT_DETECTION_NOT_SUPPORTED: + return "not-supported"; + case CONTAMINANT_DETECTION_DISABLED: + return "disabled"; + case CONTAMINANT_DETECTION_DETECTED: + return "detected"; + case CONTAMINANT_DETECTION_NOT_DETECTED: + return "not detected"; + default: + return Integer.toString(contaminantPresenceStatus); + } + } + + /** @hide */ public static String roleCombinationsToString(int combo) { StringBuilder result = new StringBuilder(); result.append("["); @@ -264,6 +330,11 @@ public final class UsbPort { @Override public String toString() { - return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}"; + return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes + + "supportsEnableContaminantPresenceProtection=" + + mSupportsEnableContaminantPresenceProtection + + "supportsEnableContaminantPresenceDetection=" + + mSupportsEnableContaminantPresenceDetection; } } diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index d30201a597bf..426dba88c399 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -39,6 +39,8 @@ public final class UsbPortStatus implements Parcelable { private final @UsbPowerRole int mCurrentPowerRole; private final @UsbDataRole int mCurrentDataRole; private final int mSupportedRoleCombinations; + private final @ContaminantProtectionStatus int mContaminantProtectionStatus; + private final @ContaminantDetectionStatus int mContaminantDetectionStatus; /** * Power role: This USB port does not have a power role. @@ -131,6 +133,93 @@ public final class UsbPortStatus implements Parcelable { public static final int MODE_DEBUG_ACCESSORY = android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; + /** + * Contaminant presence detection not supported by the device. + * @hide + */ + public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = + android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED; + + /** + * Contaminant presence detection supported but disabled. + * @hide + */ + public static final int CONTAMINANT_DETECTION_DISABLED = + android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED; + + /** + * Contaminant presence enabled but not detected. + * @hide + */ + public static final int CONTAMINANT_DETECTION_NOT_DETECTED = + android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED; + + /** + * Contaminant presence enabled and detected. + * @hide + */ + public static final int CONTAMINANT_DETECTION_DETECTED = + android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED; + + /** + * Contaminant protection - No action performed upon detection of + * contaminant presence. + * @hide + */ + public static final int CONTAMINANT_PROTECTION_NONE = + android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE; + + /** + * Contaminant protection - Port is forced to sink upon detection of + * contaminant presence. + * @hide + */ + public static final int CONTAMINANT_PROTECTION_SINK = + android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK; + + /** + * Contaminant protection - Port is forced to source upon detection of + * contaminant presence. + * @hide + */ + public static final int CONTAMINANT_PROTECTION_SOURCE = + android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE; + + /** + * Contaminant protection - Port is disabled upon detection of + * contaminant presence. + * @hide + */ + public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = + android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE; + + /** + * Contaminant protection - Port is disabled upon detection of + * contaminant presence. + * @hide + */ + public static final int CONTAMINANT_PROTECTION_DISABLED = + android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED; + + @IntDef(prefix = { "CONTAMINANT_DETECION_" }, flag = true, value = { + CONTAMINANT_DETECTION_NOT_SUPPORTED, + CONTAMINANT_DETECTION_DISABLED, + CONTAMINANT_DETECTION_NOT_DETECTED, + CONTAMINANT_DETECTION_DETECTED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ContaminantDetectionStatus{} + + @IntDef(prefix = { "CONTAMINANT_PROTECTION_" }, flag = true, value = { + CONTAMINANT_PROTECTION_NONE, + CONTAMINANT_PROTECTION_SINK, + CONTAMINANT_PROTECTION_SOURCE, + CONTAMINANT_PROTECTION_FORCE_DISABLE, + CONTAMINANT_PROTECTION_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ContaminantProtectionStatus{} + @IntDef(prefix = { "MODE_" }, flag = true, value = { MODE_NONE, MODE_DFP, @@ -142,12 +231,15 @@ public final class UsbPortStatus implements Parcelable { @interface UsbPortMode{} /** @hide */ - public UsbPortStatus(int currentMode, @UsbPowerRole int currentPowerRole, - @UsbDataRole int currentDataRole, int supportedRoleCombinations) { + public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; mCurrentDataRole = currentDataRole; mSupportedRoleCombinations = supportedRoleCombinations; + mContaminantProtectionStatus = contaminantProtectionStatus; + mContaminantDetectionStatus = contaminantDetectionStatus; } /** @@ -212,6 +304,24 @@ public final class UsbPortStatus implements Parcelable { return mSupportedRoleCombinations; } + /** + * Returns contaminant detection status. + * + * @hide + */ + public @ContaminantDetectionStatus int getContaminantDetectionStatus() { + return mContaminantDetectionStatus; + } + + /** + * Returns contamiant protection status. + * + * @hide + */ + public @ContaminantProtectionStatus int getContaminantProtectionStatus() { + return mContaminantProtectionStatus; + } + @Override public String toString() { return "UsbPortStatus{connected=" + isConnected() @@ -220,6 +330,10 @@ public final class UsbPortStatus implements Parcelable { + ", currentDataRole=" + UsbPort.dataRoleToString(mCurrentDataRole) + ", supportedRoleCombinations=" + UsbPort.roleCombinationsToString(mSupportedRoleCombinations) + + ", contaminantDetectionStatus=" + + getContaminantDetectionStatus() + + ", contaminantProtectionStatus=" + + getContaminantProtectionStatus() + "}"; } @@ -234,6 +348,8 @@ public final class UsbPortStatus implements Parcelable { dest.writeInt(mCurrentPowerRole); dest.writeInt(mCurrentDataRole); dest.writeInt(mSupportedRoleCombinations); + dest.writeInt(mContaminantProtectionStatus); + dest.writeInt(mContaminantDetectionStatus); } public static final Parcelable.Creator<UsbPortStatus> CREATOR = @@ -244,8 +360,11 @@ public final class UsbPortStatus implements Parcelable { int currentPowerRole = in.readInt(); int currentDataRole = in.readInt(); int supportedRoleCombinations = in.readInt(); + int contaminantProtectionStatus = in.readInt(); + int contaminantDetectionStatus = in.readInt(); return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, - supportedRoleCombinations); + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus); } @Override diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 103069474445..37b25c8fec0c 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -179,19 +179,24 @@ class IInputMethodWrapper extends IInputMethod.Stub return; case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; - final int missingMethods = msg.arg1; - final boolean restarting = msg.arg2 != 0; final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; + SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( - mTarget, inputContext, missingMethods, isUnbindIssued) : null; + mTarget, inputContext, moreArgs.argi3, isUnbindIssued) + : null; info.makeCompatible(mTargetSdkVersion); - inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */, - startInputToken); + inputMethod.dispatchStartInputWithToken( + ic, + info, + moreArgs.argi1 == 1 /* restarting */, + startInputToken, + moreArgs.argi2 == 1 /* shouldPreRenderIme */); args.recycle(); + moreArgs.recycle(); return; } case DO_CREATE_SESSION: { @@ -291,14 +296,17 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, - EditorInfo attribute, boolean restarting) { + EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { if (mIsUnbindIssued == null) { Log.e(TAG, "startInput must be called after bindInput."); mIsUnbindIssued = new AtomicBoolean(); } - mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT, - missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute, - mIsUnbindIssued)); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = restarting ? 1 : 0; + args.argi2 = shouldPreRenderIme ? 1 : 0; + args.argi3 = missingMethods; + mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( + DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index d3509d5eb289..5b3ad77dbf43 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -346,6 +346,13 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_VISIBLE = 0x2; + /** + * @hide + * The IME is active and ready with views but set invisible. + * This flag cannot be combined with {@link #IME_VISIBLE}. + */ + public static final int IME_INVISIBLE = 0x4; + // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; @@ -362,10 +369,19 @@ public class InputMethodService extends AbstractInputMethodService { View mRootView; SoftInputWindow mWindow; boolean mInitialized; - boolean mWindowCreated; - boolean mWindowVisible; - boolean mWindowWasVisible; + boolean mViewsCreated; + // IME views visibility. + boolean mDecorViewVisible; + boolean mDecorViewWasVisible; boolean mInShowWindow; + // True if pre-rendering of IME views/window is supported. + boolean mCanPreRender; + // If IME is pre-rendered. + boolean mIsPreRendered; + // IME window visibility. + // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user. + boolean mWindowVisible; + ViewGroup mFullscreenArea; FrameLayout mExtractFrame; FrameLayout mCandidatesFrame; @@ -552,15 +568,18 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, + public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldPreRenderIme) { mPrivOps.reportStartInput(startInputToken); - // This needs to be dispatched to interface methods rather than doStartInput(). - // Otherwise IME developers who have overridden those interface methods will lose - // notifications. - super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting, - startInputToken); + mCanPreRender = shouldPreRenderIme; + if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender); + + if (restarting) { + restartInput(inputConnection, editorInfo); + } else { + startInput(inputConnection, editorInfo); + } } /** @@ -570,14 +589,27 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "hideSoftInput()"); - boolean wasVis = isInputViewShown(); - mShowInputFlags = 0; - mShowInputRequested = false; - doHideWindow(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window invisible"); + } + setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + } else { + mShowInputFlags = 0; + mShowInputRequested = false; + doHideWindow(); + } + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_HIDDEN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -589,17 +621,28 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "showSoftInput()"); - boolean wasVis = isInputViewShown(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - showWindow(true); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window visible"); + } + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + showWindow(true); + } } // If user uses hard keyboard, IME button should always be shown. - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); - + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_SHOWN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -972,7 +1015,7 @@ public class InputMethodService extends AbstractInputMethodService { void initViews() { mInitialized = false; - mWindowCreated = false; + mViewsCreated = false; mShowInputRequested = false; mShowInputFlags = 0; @@ -1046,7 +1089,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void resetStateForNewConfiguration() { - boolean visible = mWindowVisible; + boolean visible = mDecorViewVisible; int showFlags = mShowInputFlags; boolean showingInput = mShowInputRequested; CompletionInfo[] completions = mCurCompletions; @@ -1129,7 +1172,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } mBackDisposition = disposition; - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } /** @@ -1380,7 +1423,7 @@ public class InputMethodService extends AbstractInputMethodService { mExtractFrame.setVisibility(View.GONE); } updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); - if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) { + if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) { int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation, @@ -1439,7 +1482,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void updateInputViewShown() { boolean isShown = mShowInputRequested && onEvaluateInputViewShown(); - if (mIsInputViewShown != isShown && mWindowVisible) { + if (mIsInputViewShown != isShown && mDecorViewVisible) { mIsInputViewShown = isShown; mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE); if (mInputView == null) { @@ -1458,14 +1501,14 @@ public class InputMethodService extends AbstractInputMethodService { public boolean isShowInputRequested() { return mShowInputRequested; } - + /** * Return whether the soft input view is <em>currently</em> shown to the * user. This is the state that was last determined and * applied by {@link #updateInputViewShown()}. */ public boolean isInputViewShown() { - return mIsInputViewShown && mWindowVisible; + return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible; } /** @@ -1499,7 +1542,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void setCandidatesViewShown(boolean shown) { updateCandidatesVisibility(shown); - if (!mShowInputRequested && mWindowVisible != shown) { + if (!mShowInputRequested && mDecorViewVisible != shown) { // If we are being asked to show the candidates view while the app // has not asked for the input view to be shown, then we need // to update whether the window is shown. @@ -1804,7 +1847,8 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested - + " mWindowCreated=" + mWindowCreated + + " mViewsCreated=" + mViewsCreated + + " mDecorViewVisible=" + mDecorViewVisible + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted + " mShowInputFlags=" + mShowInputFlags); @@ -1814,18 +1858,42 @@ public class InputMethodService extends AbstractInputMethodService { return; } - mWindowWasVisible = mWindowVisible; + mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; - showWindowInner(showInput); - mWindowWasVisible = true; + boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible; + final int previousImeWindowStatus = + (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown() + ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0); + startViews(prepareWindow(showInput)); + final int nextImeWindowStatus = mapToImeWindowStatus(); + if (previousImeWindowStatus != nextImeWindowStatus) { + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); + } + + // compute visibility + onWindowShown(); + mIsPreRendered = mCanPreRender; + if (mIsPreRendered) { + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + // Pre-rendering not supported. + if (DEBUG) Log.d(TAG, "No pre-rendering supported"); + mWindowVisible = true; + } + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if ((previousImeWindowStatus & IME_ACTIVE) == 0) { + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + } + mDecorViewWasVisible = true; mInShowWindow = false; } - void showWindowInner(boolean showInput) { + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; - final int previousImeWindowStatus = - (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0); - mWindowVisible = true; + mDecorViewVisible = true; if (!mShowInputRequested && mInputStarted && showInput) { doShowInput = true; mShowInputRequested = true; @@ -1836,8 +1904,8 @@ public class InputMethodService extends AbstractInputMethodService { updateFullscreenMode(); updateInputViewShown(); - if (!mWindowCreated) { - mWindowCreated = true; + if (!mViewsCreated) { + mViewsCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); View v = onCreateCandidatesView(); @@ -1846,6 +1914,10 @@ public class InputMethodService extends AbstractInputMethodService { setCandidatesView(v); } } + return doShowInput; + } + + private void startViews(boolean doShowInput) { if (mShowInputRequested) { if (!mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); @@ -1857,29 +1929,26 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, false); } - - if (doShowInput) { - startExtractingText(false); - } + if (doShowInput) startExtractingText(false); + } - final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); - if (previousImeWindowStatus != nextImeWindowStatus) { - setImeWindowStatus(nextImeWindowStatus, mBackDisposition); - } - if ((previousImeWindowStatus & IME_ACTIVE) == 0) { - if (DEBUG) Log.v(TAG, "showWindow: showing!"); + private void onPreRenderedWindowVisibilityChanged(boolean setVisible) { + mWindowVisible = setVisible; + mShowInputFlags = setVisible ? mShowInputFlags : 0; + mShowInputRequested = setVisible; + mDecorViewVisible = setVisible; + if (setVisible) { onWindowShown(); - mWindow.show(); } } - private void finishViews() { + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(false); + onFinishInputView(finishingInput); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(false); + onFinishCandidatesView(finishingInput); } mInputViewStarted = false; mCandidatesViewStarted = false; @@ -1891,12 +1960,15 @@ public class InputMethodService extends AbstractInputMethodService { } public void hideWindow() { - finishViews(); - if (mWindowVisible) { + if (DEBUG) Log.v(TAG, "CALL: hideWindow"); + mIsPreRendered = false; + mWindowVisible = false; + finishViews(false /* finishingInput */); + if (mDecorViewVisible) { mWindow.hide(); - mWindowVisible = false; + mDecorViewVisible = false; onWindowHidden(); - mWindowWasVisible = false; + mDecorViewWasVisible = false; } updateFullscreenMode(); } @@ -1956,15 +2028,8 @@ public class InputMethodService extends AbstractInputMethodService { } void doFinishInput() { - if (mInputViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(true); - } else if (mCandidatesViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(true); - } - mInputViewStarted = false; - mCandidatesViewStarted = false; + if (DEBUG) Log.v(TAG, "CALL: doFinishInput"); + finishViews(true /* finishingInput */); if (mInputStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); onFinishInput(); @@ -1984,7 +2049,7 @@ public class InputMethodService extends AbstractInputMethodService { initialize(); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(attribute, restarting); - if (mWindowVisible) { + if (mDecorViewVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; @@ -1995,6 +2060,31 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, restarting); } + } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) { + // Pre-render IME views and window when real EditorInfo is available. + // pre-render IME window and keep it invisible. + if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName); + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + mDecorViewWasVisible = mDecorViewVisible; + mInShowWindow = true; + startViews(prepareWindow(true /* showInput */)); + + // compute visibility + mIsPreRendered = true; + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + mDecorViewWasVisible = true; + mInShowWindow = false; + } else { + mIsPreRendered = false; } } @@ -2087,7 +2177,14 @@ public class InputMethodService extends AbstractInputMethodService { * protocol, so applications with custom text editing written before this method appeared will * not call to inform the IME of this interaction. * @param focusChanged true if the user changed the focused view by this click. + * @see InputMethodManager#viewClicked(View) + * @deprecated The method may not be called for composite {@link View} that works as a giant + * "Canvas", which can host its own UI hierarchy and sub focus state. + * {@link android.webkit.WebView} is a good example. Application / IME developers + * should not rely on this method. If your goal is just being notified when an + * on-going input is interrupted, simply monitor {@link #onFinishInput()}. */ + @Deprecated public void onViewClicked(boolean focusChanged) { // Intentionally empty } @@ -2146,7 +2243,7 @@ public class InputMethodService extends AbstractInputMethodService { // consume the back key. if (doIt) requestHideSelf(0); return true; - } else if (mWindowVisible) { + } else if (mDecorViewVisible) { if (mCandidatesVisibility == View.VISIBLE) { // If we are showing candidates even if no input area, then // hide them. @@ -2173,7 +2270,6 @@ public class InputMethodService extends AbstractInputMethodService { return mExtractEditText; } - /** * Called back when a {@link KeyEvent} is forwarded from the target application. * @@ -2886,8 +2982,11 @@ public class InputMethodService extends AbstractInputMethodService { inputContentInfo.setUriToken(uriToken); } - private static int mapToImeWindowStatus(boolean isInputViewShown) { - return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0); + private int mapToImeWindowStatus() { + return IME_ACTIVE + | (isInputViewShown() + ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE) + : IME_VISIBLE) : 0); } /** @@ -2897,9 +2996,10 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); - p.println(" mWindowCreated=" + mWindowCreated); - p.println(" mWindowVisible=" + mWindowVisible - + " mWindowWasVisible=" + mWindowWasVisible + p.println(" mViewsCreated=" + mViewsCreated); + p.println(" mDecorViewVisible=" + mDecorViewVisible + + " mDecorViewWasVisible=" + mDecorViewWasVisible + + " mWindowVisible=" + mWindowVisible + " mInShowWindow=" + mInShowWindow); p.println(" Configuration=" + getResources().getConfiguration()); p.println(" mToken=" + mToken); @@ -2919,6 +3019,8 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mShowInputRequested=" + mShowInputRequested + " mLastShowInputRequested=" + mLastShowInputRequested + + " mCanPreRender=" + mCanPreRender + + " mIsPreRendered=" + mIsPreRendered + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags)); p.println(" mCandidatesVisibility=" + mCandidatesVisibility + " mFullscreenApplied=" + mFullscreenApplied diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c809ccad5907..5bb24bab6e48 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -15,6 +15,9 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; + +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +31,8 @@ import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.SocketKeepalive.Callback; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -58,6 +63,7 @@ import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; +import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; @@ -66,6 +72,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * Class that answers queries about the state of network connectivity. It also @@ -1699,6 +1706,8 @@ public class ConnectivityManager { * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or * {@link PacketKeepaliveCallback#onError} if an error occurred. * + * @deprecated Use {@link SocketKeepalive} instead. + * * @hide */ public class PacketKeepalive { @@ -1802,6 +1811,8 @@ public class ConnectivityManager { /** * Starts an IPsec NAT-T keepalive packet with the specified parameters. * + * @deprecated Use {@link #createSocketKeepalive} instead. + * * @hide */ @UnsupportedAppUsage @@ -1821,6 +1832,62 @@ public class ConnectivityManager { } /** + * Request that keepalives be started on a IPsec NAT-T socket. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object. + **/ + public SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(), + socket.getResourceId(), source, destination, executor, callback); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called + * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}. + * + * @param network The {@link Network} the socket is on. + * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided + * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from + * that port. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. The + * keepalive packets will always be sent to port 4500 of the given {@code destination}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull FileDescriptor fd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */, + source, destination, executor, callback); + } + + /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. An attempt to add a route that * already exists is ignored, but treated as successful. diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java new file mode 100644 index 000000000000..458fb340b196 --- /dev/null +++ b/core/java/android/net/DnsPacket.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; + +import com.android.internal.util.BitUtils; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * Defines basic data for DNS protocol based on RFC 1035. + * Subclasses create the specific format used in DNS packet. + * + * @hide + */ +public abstract class DnsPacket { + public class DnsHeader { + private static final String TAG = "DnsHeader"; + public final int id; + public final int flags; + public final int rcode; + private final int[] mSectionCount; + + /** + * Create a new DnsHeader from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS header. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { + id = BitUtils.uint16(buf.getShort()); + flags = BitUtils.uint16(buf.getShort()); + rcode = flags & 0xF; + mSectionCount = new int[NUM_SECTIONS]; + for (int i = 0; i < NUM_SECTIONS; ++i) { + mSectionCount[i] = BitUtils.uint16(buf.getShort()); + } + } + + /** + * Get section count by section type. + */ + public int getSectionCount(int sectionType) { + return mSectionCount[sectionType]; + } + } + + public class DnsSection { + private static final int MAXNAMESIZE = 255; + private static final int MAXLABELSIZE = 63; + private static final int MAXLABELCOUNT = 128; + private static final int NAME_NORMAL = 0; + private static final int NAME_COMPRESSION = 0xC0; + private final DecimalFormat byteFormat = new DecimalFormat(); + private final FieldPosition pos = new FieldPosition(0); + + private static final String TAG = "DnsSection"; + + public final String dName; + public final int nsType; + public final int nsClass; + public final long ttl; + private final byte[] mRR; + + /** + * Create a new DnsSection from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS section. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsSection(int sectionType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + dName = parseName(buf, 0 /* Parse depth */); + if (dName.length() > MAXNAMESIZE) { + throw new ParseException("Parse name fail, name size is too long"); + } + nsType = BitUtils.uint16(buf.getShort()); + nsClass = BitUtils.uint16(buf.getShort()); + + if (sectionType != QDSECTION) { + ttl = BitUtils.uint32(buf.getInt()); + final int length = BitUtils.uint16(buf.getShort()); + mRR = new byte[length]; + buf.get(mRR); + } else { + ttl = 0; + mRR = null; + } + } + + /** + * Get a copy of rr. + */ + @Nullable public byte[] getRR() { + return (mRR == null) ? null : mRR.clone(); + } + + /** + * Convert label from {@code byte[]} to {@code String} + * + * It follows the same converting rule as native layer. + * (See ns_name.c in libc) + * + */ + private String labelToString(@NonNull byte[] label) { + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < label.length; ++i) { + int b = BitUtils.uint8(label[i]); + // Control characters and non-ASCII characters. + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + byteFormat.format(b, sb, pos); + } else if (b == '"' || b == '.' || b == ';' || b == '\\' + || b == '(' || b == ')' || b == '@' || b == '$') { + sb.append('\\'); + sb.append((char) b); + } else { + sb.append((char) b); + } + } + return sb.toString(); + } + + private String parseName(@NonNull ByteBuffer buf, int depth) throws + BufferUnderflowException, ParseException { + if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); + final int len = BitUtils.uint8(buf.get()); + final int mask = len & NAME_COMPRESSION; + if (0 == len) { + return ""; + } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { + throw new ParseException("Parse name fail, bad label type"); + } else if (mask == NAME_COMPRESSION) { + // Name compression based on RFC 1035 - 4.1.4 Message compression + final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); + final int oldPos = buf.position(); + if (offset >= oldPos - 2) { + throw new ParseException("Parse compression name fail, invalid compression"); + } + buf.position(offset); + final String pointed = parseName(buf, depth + 1); + buf.position(oldPos); + return pointed; + } else { + final byte[] label = new byte[len]; + buf.get(label); + final String head = labelToString(label); + if (head.length() > MAXLABELSIZE) { + throw new ParseException("Parse name fail, invalid label length"); + } + final String tail = parseName(buf, depth + 1); + return TextUtils.isEmpty(tail) ? head : head + "." + tail; + } + } + } + + public static final int QDSECTION = 0; + public static final int ANSECTION = 1; + public static final int NSSECTION = 2; + public static final int ARSECTION = 3; + private static final int NUM_SECTIONS = ARSECTION + 1; + + private static final String TAG = DnsPacket.class.getSimpleName(); + + protected final DnsHeader mHeader; + protected final List<DnsSection>[] mSections; + + public static class ParseException extends Exception { + public ParseException(String msg) { + super(msg); + } + + public ParseException(String msg, Throwable cause) { + super(msg, cause); + } + } + + protected DnsPacket(@NonNull byte[] data) throws ParseException { + if (null == data) throw new ParseException("Parse header failed, null input data"); + final ByteBuffer buffer; + try { + buffer = ByteBuffer.wrap(data); + mHeader = new DnsHeader(buffer); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse Header fail, bad input data", e); + } + + mSections = new ArrayList[NUM_SECTIONS]; + + for (int i = 0; i < NUM_SECTIONS; ++i) { + final int count = mHeader.getSectionCount(i); + if (count > 0) { + mSections[i] = new ArrayList(count); + } + for (int j = 0; j < count; ++j) { + try { + mSections[i].add(new DnsSection(i, buffer)); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse section fail", e); + } + } + } + } +} diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java new file mode 100644 index 000000000000..6d54264cd89f --- /dev/null +++ b/core/java/android/net/DnsResolver.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.NetworkUtils.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + + +/** + * Dns resolver class for asynchronous dns querying + * + */ +public final class DnsResolver { + private static final String TAG = "DnsResolver"; + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int MAXPACKET = 8 * 1024; + + @IntDef(prefix = { "CLASS_" }, value = { + CLASS_IN + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryClass {} + public static final int CLASS_IN = 1; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_A, + TYPE_AAAA + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + public static final int TYPE_A = 1; + public static final int TYPE_AAAA = 28; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_EMPTY, + FLAG_NO_RETRY, + FLAG_NO_CACHE_STORE, + FLAG_NO_CACHE_LOOKUP + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryFlag {} + public static final int FLAG_EMPTY = 0; + public static final int FLAG_NO_RETRY = 1 << 0; + public static final int FLAG_NO_CACHE_STORE = 1 << 1; + public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; + + private static final int DNS_RAW_RESPONSE = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * listener for receiving raw answers + */ + public interface RawAnswerListener { + /** + * {@code byte[]} is {@code null} if query timed out + */ + void onAnswer(@Nullable byte[] answer); + } + + /** + * listener for receiving parsed answers + */ + public interface InetAddressAnswerListener { + /** + * Will be called exactly once with all the answers to the query. + * size of addresses will be zero if no available answer could be parsed. + */ + void onAnswer(@NonNull List<InetAddress> addresses); + } + + /** + * Get instance for DnsResolver + */ + public static DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Pass in a blob and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param query blob message + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkSend((network != null + ? network.netId : NETID_UNSET), query, query.length, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param nsClass dns class as one of the CLASS_* constants + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass, + @QueryType int nsType, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get back a set of InetAddresses asynchronously. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link InetAddressAnswerListener} will be invoked. + * @param listener an {@link InetAddressAnswerListener} which will be called to + * notify the caller of the result of dns query. + * + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull Handler handler, @NonNull InetAddressAnswerListener listener) + throws ErrnoException { + final FileDescriptor v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + final FileDescriptor v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + + final InetAddressAnswerAccumulator accmulator = + new InetAddressAnswerAccumulator(2, listener); + final Consumer<byte[]> consumer = answerbuf -> + accmulator.accumulate(parseAnswers(answerbuf)); + + registerFDListener(handler.getLooper().getQueue(), v4fd, consumer); + registerFDListener(handler.getLooper().getQueue(), v6fd, consumer); + } + + private void registerFDListener(@NonNull MessageQueue queue, + @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) { + queue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + byte[] answerbuf = null; + try { + // TODO: Implement result function in Java side instead of using JNI + // Because JNI method close fd prior than unregistering fd on + // event listener. + answerbuf = resNetworkResult(fd); + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + } + answerConsumer.accept(answerbuf); + + // Unregister this fd listener + return 0; + }); + } + + private class DnsAddressAnswer extends DnsPacket { + private static final String TAG = "DnsResolver.DnsAddressAnswer"; + private static final boolean DBG = false; + + private final int mQueryType; + + DnsAddressAnswer(@NonNull byte[] data) throws ParseException { + super(data); + if ((mHeader.flags & (1 << 15)) == 0) { + throw new ParseException("Not an answer packet"); + } + if (mHeader.rcode != 0) { + throw new ParseException("Response error, rcode:" + mHeader.rcode); + } + if (mHeader.getSectionCount(ANSECTION) == 0) { + throw new ParseException("No available answer"); + } + if (mHeader.getSectionCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Assume only one question per answer packet. (RFC1035) + mQueryType = mSections[QDSECTION].get(0).nsType; + } + + public @NonNull List<InetAddress> getAddresses() { + final List<InetAddress> results = new ArrayList<InetAddress>(); + for (final DnsSection ansSec : mSections[ANSECTION]) { + // Only support A and AAAA, also ignore answers if query type != answer type. + int nsType = ansSec.nsType; + if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { + continue; + } + try { + results.add(InetAddress.getByAddress(ansSec.getRR())); + } catch (UnknownHostException e) { + if (DBG) { + Log.w(TAG, "rr to address fail"); + } + } + } + return results; + } + } + + private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) { + try { + return (data == null) ? null : new DnsAddressAnswer(data).getAddresses(); + } catch (DnsPacket.ParseException e) { + Log.e(TAG, "Parse answer fail " + e.getMessage()); + return null; + } + } + + private class InetAddressAnswerAccumulator { + private final List<InetAddress> mAllAnswers; + private final InetAddressAnswerListener mAnswerListener; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mAnswerListener = listener; + } + + public void accumulate(@Nullable List<InetAddress> answer) { + if (null != answer) { + mAllAnswers.addAll(answer); + } + if (++mReceivedAnswerCount == mTargetAnswerCount) { + mAnswerListener.onAnswer(mAllAnswers); + } + } + } +} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 131925ec28e9..e97060a0a599 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -181,6 +181,10 @@ interface IConnectivityManager void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, int srcPort, String dstAddr); + void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId, + int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, + String dstAddr); + void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index b7af37474307..f0fe92eb8641 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -24,7 +24,7 @@ import android.net.RouteInfo; * * @hide */ -interface INetworkManagementEventObserver { +oneway interface INetworkManagementEventObserver { /** * Interface configuration status has changed. * diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index 2df8ab7ec198..8b64f1c7c45a 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -18,10 +18,12 @@ package android.net; import android.net.INetworkMonitorCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; /** @hide */ oneway interface INetworkStackConnector { void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, in IDhcpServerCallbacks cb); void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb); + void makeIpClient(in String ifName, in IIpClientCallbacks callbacks); }
\ No newline at end of file diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 4631c565962f..b996cdab5164 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -83,6 +85,8 @@ public final class IpPrefix implements Parcelable { * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ + @SystemApi + @TestApi public IpPrefix(InetAddress address, int prefixLength) { // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, // which is unnecessary because getAddress() already returns a clone. diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index a536d08876d6..fbd602c7b2d0 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -162,6 +162,8 @@ public class LinkAddress implements Parcelable { * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). * @hide */ + @SystemApi + @TestApi public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) { init(address, prefixLength, flags, scope); } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 21b6a8eb1990..662870182eea 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -174,7 +174,8 @@ public final class LinkProperties implements Parcelable { /** * @hide */ - @UnsupportedAppUsage + @SystemApi + @TestApi public LinkProperties(LinkProperties source) { if (source != null) { mIfaceName = source.mIfaceName; @@ -576,6 +577,8 @@ public final class LinkProperties implements Parcelable { * @param addresses The {@link Collection} of PCSCF servers to set in this object. * @hide */ + @SystemApi + @TestApi public void setPcscfServers(Collection<InetAddress> pcscfServers) { mPcscfs.clear(); for (InetAddress pcscfServer: pcscfServers) { @@ -590,6 +593,8 @@ public final class LinkProperties implements Parcelable { * this link. * @hide */ + @SystemApi + @TestApi public List<InetAddress> getPcscfServers() { return Collections.unmodifiableList(mPcscfs); } @@ -781,6 +786,8 @@ public final class LinkProperties implements Parcelable { * @return the NAT64 prefix. * @hide */ + @SystemApi + @TestApi public @Nullable IpPrefix getNat64Prefix() { return mNat64Prefix; } @@ -794,6 +801,8 @@ public final class LinkProperties implements Parcelable { * @param prefix the NAT64 prefix. * @hide */ + @SystemApi + @TestApi public void setNat64Prefix(IpPrefix prefix) { if (prefix != null && prefix.getPrefixLength() != 96) { throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix); diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java new file mode 100644 index 000000000000..88631aea3a88 --- /dev/null +++ b/core/java/android/net/NattSocketKeepalive.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 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.net; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.util.concurrent.Executor; + +/** @hide */ +public final class NattSocketKeepalive extends SocketKeepalive { + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + @NonNull private final InetAddress mSource; + @NonNull private final InetAddress mDestination; + @NonNull private final FileDescriptor mFd; + private final int mResourceId; + + NattSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull FileDescriptor fd, + int resourceId, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, executor, callback); + mSource = source; + mDestination = destination; + mFd = fd; + mResourceId = resourceId; + } + + @Override + void startImpl(int intervalSec) { + try { + mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger, + new Binder(), mSource.getHostAddress(), mDestination.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + stopLooper(); + } + } + + @Override + void stopImpl() { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + stopLooper(); + } + } +} diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index af043eeccde2..d277034650a1 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; import android.os.Binder; import android.os.IBinder; import android.os.Process; @@ -84,6 +85,21 @@ public class NetworkStack { } /** + * Create an IpClient on the specified interface. + * + * <p>The IpClient will be returned asynchronously through the provided callbacks. + */ + public void makeIpClient(String ifName, IIpClientCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeIpClient(ifName, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + /** * Create a NetworkMonitor. * * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 4eab49cd0fdf..c996d01fe8e4 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -39,6 +39,8 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; +import android.system.ErrnoException; + /** * Native methods for managing network interfaces. * @@ -138,6 +140,32 @@ public class NetworkUtils { public native static boolean queryUserAccess(int uid, int netId); /** + * DNS resolver series jni method. + * Issue the query {@code msg} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated + * with Domain Name {@code dname} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Read a result for the query associated with the {@code fd}. + * @return a byte array containing blob answer + */ + public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException; + + /** * Add an entry into the ARP cache. */ public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index e926fda336f4..ef2269a145d0 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -39,12 +39,12 @@ import java.util.Locale; */ public class ProxyInfo implements Parcelable { - private String mHost; - private int mPort; - private String mExclusionList; - private String[] mParsedExclusionList; + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; - private Uri mPacFileUrl; /** *@hide */ @@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String host, int port, String exclList) { mHost = host; mPort = port; - setExclusionList(exclList); + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.EMPTY; } @@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } @@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.parse(pacFileUrl); } @@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl, int localProxyPort) { mHost = LOCAL_HOST; mPort = localProxyPort; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } mPacFileUrl = pacFileUrl; } + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; @@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable { mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; mPacFileUrl = Uri.EMPTY; } } @@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable { return mExclusionList; } - // comma separated - private void setExclusionList(String exclusionList) { - mExclusionList = exclusionList; - if (mExclusionList == null) { - mParsedExclusionList = new String[0]; - } else { - mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(","); - } - } - /** * @hide */ public boolean isValid() { if (!Uri.EMPTY.equals(mPacFileUrl)) return true; return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, - mPort == 0 ? "" : Integer.toString(mPort), - mExclusionList == null ? "" : mExclusionList); + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); } /** @@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable { sb.append("] "); sb.append(Integer.toString(mPort)); if (mExclusionList != null) { - sb.append(" xl=").append(mExclusionList); + sb.append(" xl=").append(mExclusionList); } } else { sb.append("[ProxyProperties.mHost == null]"); @@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable { */ public int hashCode() { return ((null == mHost) ? 0 : mHost.hashCode()) - + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) - + mPort; + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; } /** @@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable { } String exclList = in.readString(); String[] parsedExclList = in.readStringArray(); - ProxyInfo proxyProperties = - new ProxyInfo(host, port, exclList, parsedExclList); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index 6bf2c67da990..5c0f7582091d 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -110,6 +110,8 @@ public final class RouteInfo implements Parcelable { * * @hide */ + @SystemApi + @TestApi public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) { switch (type) { case RTN_UNICAST: diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java new file mode 100644 index 000000000000..97d50f4bac05 --- /dev/null +++ b/core/java/android/net/SocketKeepalive.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive}, + * passing in a non-null callback. If the {@link SocketKeepalive} is successfully + * started, the callback's {@code onStarted} method will be called. If an error occurs, + * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this + * class. + * + * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call + * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or + * {@link SocketKeepalive.Callback#onError} if an error occurred. + */ +public abstract class SocketKeepalive implements AutoCloseable { + static final String TAG = "SocketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int DATA_RECEIVED = -2; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + /** The target socket is invalid. */ + public static final int ERROR_INVALID_SOCKET = -25; + /** The target socket is not idle. */ + public static final int ERROR_SOCKET_NOT_IDLE = -26; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_NETWORK, + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH, + ERROR_INVALID_INTERVAL, + ERROR_INVALID_SOCKET, + ERROR_SOCKET_NOT_IDLE + }) + public @interface ErrorCode {} + + /** + * The minimum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MIN_INTERVAL_SEC = 10; + + /** + * The maximum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MAX_INTERVAL_SEC = 3600; + + @NonNull final IConnectivityManager mService; + @NonNull final Network mNetwork; + @NonNull private final Executor mExecutor; + @NonNull private final SocketKeepalive.Callback mCallback; + @NonNull private final Looper mLooper; + @NonNull final Messenger mMessenger; + @NonNull Integer mSlot; + + SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, + @NonNull Executor executor, @NonNull Callback callback) { + mService = service; + mNetwork = network; + mExecutor = executor; + mCallback = callback; + // TODO: 1. Use other thread modeling instead of create one thread for every instance to + // reduce the memory cost. + // 2. support restart. + // 3. Fix race condition which caused by rapidly start and stop. + HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND + + Process.THREAD_PRIORITY_LESS_FAVORABLE); + thread.start(); + mLooper = thread.getLooper(); + mMessenger = new Messenger(new Handler(mLooper) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case NetworkAgent.EVENT_PACKET_KEEPALIVE: + final int status = message.arg2; + try { + if (status == SUCCESS) { + if (mSlot == null) { + mSlot = message.arg1; + mExecutor.execute(() -> mCallback.onStarted()); + } else { + mSlot = null; + stopLooper(); + mExecutor.execute(() -> mCallback.onStopped()); + } + } else if (status == DATA_RECEIVED) { + stopLooper(); + mExecutor.execute(() -> mCallback.onDataReceived()); + } else { + stopLooper(); + mExecutor.execute(() -> mCallback.onError(status)); + } + } catch (Exception e) { + Log.e(TAG, "Exception in keepalive callback(" + status + ")", e); + } + break; + default: + Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what)); + break; + } + } + }); + } + + /** + * Request that keepalive be started with the given {@code intervalSec}. See + * {@link SocketKeepalive}. + * + * @param intervalSec The target interval in seconds between keepalive packet transmissions. + * The interval should be between 10 seconds and 3600 seconds, otherwise + * {@link #ERROR_INVALID_INTERVAL} will be returned. + */ + public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) + int intervalSec) { + startImpl(intervalSec); + } + + abstract void startImpl(int intervalSec); + + /** @hide */ + protected void stopLooper() { + // TODO: remove this after changing thread modeling. + mLooper.quit(); + } + + /** + * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped} + * before using the object. See {@link SocketKeepalive}. + */ + public final void stop() { + stopImpl(); + } + + abstract void stopImpl(); + + /** + * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be + * usable again if {@code close()} is called. + */ + @Override + public final void close() { + stop(); + stopLooper(); + } + + /** + * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See + * {@link SocketKeepalive}. + */ + public static class Callback { + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(@ErrorCode int error) {} + /** The keepalive on a TCP socket was stopped because the socket received data. */ + public void onDataReceived() {} + } +} diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 37bf3a71ce62..dc099a46aa2a 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -509,6 +509,15 @@ public class VpnService extends Service { } /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + public Builder setHttpProxy(ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + + /** * Add a network address to the VPN interface. Both IPv4 and IPv6 * addresses are supported. At least one address must be set before * calling {@link #establish}. diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java index 5397b57a4568..6a9eae00e3ff 100644 --- a/core/java/android/net/ipmemorystore/NetworkAttributes.java +++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java @@ -252,6 +252,12 @@ public class NetworkAttributes { } } + /** @hide */ + public boolean isEmpty() { + return (null == assignedV4Address) && (null == groupHint) + && (null == dnsAddresses) && (null == mtu); + } + @Override public boolean equals(@Nullable final Object o) { if (!(o instanceof NetworkAttributes)) return false; diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index 16aea31b97c9..5b5a23578954 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -18,7 +18,6 @@ package android.net.metrics; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.Network; @@ -51,7 +50,8 @@ public class IpConnectivityLog { public interface Event extends Parcelable {} /** @hide */ - @UnsupportedAppUsage + @SystemApi + @TestApi public IpConnectivityLog() { } diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java index d308246f4d77..04a2e6e3102c 100644 --- a/core/java/android/net/metrics/RaEvent.java +++ b/core/java/android/net/metrics/RaEvent.java @@ -16,7 +16,8 @@ package android.net.metrics; -import android.annotation.UnsupportedAppUsage; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,19 +25,28 @@ import android.os.Parcelable; * An event logged when the APF packet socket receives an RA packet. * {@hide} */ +@SystemApi +@TestApi public final class RaEvent implements IpConnectivityLog.Event { - public static final long NO_LIFETIME = -1L; + private static final long NO_LIFETIME = -1L; // Lifetime in seconds of options found in a single RA packet. // When an option is not set, the value of the associated field is -1; + /** @hide */ public final long routerLifetime; + /** @hide */ public final long prefixValidLifetime; + /** @hide */ public final long prefixPreferredLifetime; + /** @hide */ public final long routeInfoLifetime; + /** @hide */ public final long rdnssLifetime; + /** @hide */ public final long dnsslLifetime; + /** @hide */ public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime, long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) { this.routerLifetime = routerLifetime; @@ -47,6 +57,7 @@ public final class RaEvent implements IpConnectivityLog.Event { this.dnsslLifetime = dnsslLifetime; } + /** @hide */ private RaEvent(Parcel in) { routerLifetime = in.readLong(); prefixValidLifetime = in.readLong(); @@ -56,6 +67,7 @@ public final class RaEvent implements IpConnectivityLog.Event { dnsslLifetime = in.readLong(); } + /** @hide */ @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(routerLifetime); @@ -66,6 +78,7 @@ public final class RaEvent implements IpConnectivityLog.Event { out.writeLong(dnsslLifetime); } + /** @hide */ @Override public int describeContents() { return 0; @@ -83,6 +96,7 @@ public final class RaEvent implements IpConnectivityLog.Event { .toString(); } + /** @hide */ public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() { public RaEvent createFromParcel(Parcel in) { return new RaEvent(in); @@ -102,47 +116,39 @@ public final class RaEvent implements IpConnectivityLog.Event { long rdnssLifetime = NO_LIFETIME; long dnsslLifetime = NO_LIFETIME; - @UnsupportedAppUsage public Builder() { } - @UnsupportedAppUsage public RaEvent build() { return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime, routeInfoLifetime, rdnssLifetime, dnsslLifetime); } - @UnsupportedAppUsage public Builder updateRouterLifetime(long lifetime) { routerLifetime = updateLifetime(routerLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updatePrefixValidLifetime(long lifetime) { prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updatePrefixPreferredLifetime(long lifetime) { prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateRouteInfoLifetime(long lifetime) { routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateRdnssLifetime(long lifetime) { rdnssLifetime = updateLifetime(rdnssLifetime, lifetime); return this; } - @UnsupportedAppUsage public Builder updateDnsslLifetime(long lifetime) { dnsslLifetime = updateLifetime(dnsslLifetime, lifetime); return this; diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java index 30c5cd98b719..30c5cd98b719 100644 --- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java +++ b/core/java/android/net/util/MultinetworkPolicyTracker.java diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java index 950f38167754..6daa5b4dc6d8 100644 --- a/core/java/android/os/AppZygote.java +++ b/core/java/android/os/AppZygote.java @@ -103,7 +103,7 @@ public class AppZygote { String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi : Build.SUPPORTED_ABIS[0]; try { - mZygote = Process.zygoteProcess.startChildZygote( + mZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.AppZygoteInit", mAppInfo.processName + "_zygote", mZygoteUid, diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 954071a0ee97..3fc5e41ad990 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,6 +16,8 @@ package android.os; +import android.Manifest.permission; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; @@ -369,4 +371,26 @@ public class BatteryManager { throw e.rethrowFromSystemServer(); } } + + /** + * Sets the delay for reporting battery state as charging after device is plugged in. + * This allows machine-learning or heuristics to delay the reporting and the corresponding + * broadcast, based on battery level, charging rate, and/or other parameters. + * + * @param delayMillis the delay in milliseconds, negative value to reset. + * + * @return True if the delay was set successfully. + * + * @see ACTION_CHARGING + * @hide + */ + @RequiresPermission(permission.POWER_SAVER) + @SystemApi + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + try { + return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index eae1aa5eb750..1919fcc00a33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -834,10 +834,10 @@ public abstract class BatteryStats implements Parcelable { * also be bumped. */ static final String[] USER_ACTIVITY_TYPES = { - "other", "button", "touch", "accessibility" + "other", "button", "touch", "accessibility", "attention" }; - public static final int NUM_USER_ACTIVITY_TYPES = 4; + public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index c5a51f133047..6f5f8072f471 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. @@ -34,7 +36,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ // TODO: Expose API when the implementation is more complete. -// @SystemApi +//@SystemApi @SystemService(Context.BUGREPORT_SERVICE) public class BugreportManager { private final Context mContext; @@ -47,86 +49,119 @@ public class BugreportManager { } /** - * An interface describing the listener for bugreport progress and status. + * An interface describing the callback for bugreport progress and status. */ - public interface BugreportListener { - /** - * Called when there is a progress update. - * @param progress the progress in [0.0, 100.0] - */ - void onProgress(float progress); - + public abstract static class BugreportCallback { + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, - BUGREPORT_ERROR_RUNTIME + BUGREPORT_ERROR_RUNTIME, + BUGREPORT_ERROR_USER_DENIED_CONSENT, + BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ - @interface BugreportErrorCode {} + public @interface BugreportErrorCode {} /** The input options were invalid */ - int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; + public static final int BUGREPORT_ERROR_INVALID_INPUT = + IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ - int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; + public static final int BUGREPORT_ERROR_RUNTIME = + IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ - int BUGREPORT_ERROR_USER_DENIED_CONSENT = + public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; + /** The request to get user consent timed out. */ + public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = + IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; + + /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + public void onProgress(float progress) {} + /** * Called when taking bugreport resulted in an error. * - * @param errorCode the error that occurred. Possible values are - * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}. + * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not + * consent to sharing the bugreport with the calling app. + * + * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed + * out, but the bugreport could be available in the internal directory of dumpstate for + * manual retrieval. */ - void onError(@BugreportErrorCode int errorCode); + public void onError(@BugreportErrorCode int errorCode) {} /** - * Called when taking bugreport finishes successfully - * - * @param durationMs time capturing bugreport took in milliseconds - * @param title title for the bugreport; helpful in reminding the user why they took it - * @param description detailed description for the bugreport + * Called when taking bugreport finishes successfully. */ - void onFinished(long durationMs, @NonNull String title, - @NonNull String description); + public void onFinished() {} } /** - * Starts a bugreport asynchronously. + * Starts a bugreport. + * + * <p>This starts a bugreport in the background. However the call itself can take several + * seconds to return in the worst case. {@code callback} will receive progress and status + * updates. + * + * <p>The bugreport artifacts will be copied over to the given file descriptors only if the + * user consents to sharing with the calling app. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, * append mode. * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param params options that specify what kind of a bugreport should be taken - * @param listener callback for progress and status updates + * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) - public void startBugreport(@NonNull FileDescriptor bugreportFd, - @Nullable FileDescriptor screenshotFd, - @NonNull BugreportParams params, @Nullable BugreportListener listener) { + public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, + @Nullable ParcelFileDescriptor screenshotFd, + @NonNull BugreportParams params, + @NonNull @CallbackExecutor Executor executor, + @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. - DumpstateListener dsListener = new DumpstateListener(listener); - + DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, - mContext.getOpPackageName(), bugreportFd, screenshotFd, + mContext.getOpPackageName(), + (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), + (screenshotFd != null + ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /* + * Cancels a currently running bugreport. + */ + @RequiresPermission(android.Manifest.permission.DUMP) + public void cancelBugreport() { + try { + mBinder.cancelBugreport(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { - private final BugreportListener mListener; + private final Executor mExecutor; + private final BugreportCallback mCallback; - DumpstateListener(@Nullable BugreportListener listener) { - mListener = listener; + DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { + mExecutor = executor; + mCallback = callback; } @Override @@ -136,18 +171,40 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { - mListener.onProgress(progress); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onProgress(progress); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onError(int errorCode) throws RemoteException { - mListener.onError(errorCode); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onError(errorCode); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override - public void onFinished(long durationMs, String title, String description) - throws RemoteException { - mListener.onFinished(durationMs, title, description); + public void onFinished() throws RemoteException { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onFinished(); + }); + } finally { + Binder.restoreCallingIdentity(identity); + // The bugreport has finished. Let's shutdown the service to minimize its footprint. + cancelBugreport(); + } } // Old methods; should go away diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 51c3c4c25ce0..629289bb7a45 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -39,6 +39,7 @@ import static android.system.OsConstants.W_OK; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ContentResolver; import android.provider.DocumentsContract.Document; import android.system.ErrnoException; @@ -852,6 +853,7 @@ public class FileUtils { * * @hide */ + @TestApi public static boolean contains(File dir, File file) { if (dir == null || file == null) return false; return contains(dir.getAbsolutePath(), file.getAbsolutePath()); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index f51ba9a41a2b..efcad3ece97d 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -23,10 +23,15 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; +import android.gamedriver.GameDriverProto.Blacklist; +import android.gamedriver.GameDriverProto.Blacklists; import android.opengl.EGL14; import android.provider.Settings; +import android.util.Base64; import android.util.Log; +import com.android.framework.protobuf.InvalidProtocolBufferException; + import dalvik.system.VMRuntime; import java.io.BufferedReader; @@ -62,6 +67,8 @@ public class GraphicsEnvironment { private static final String ANGLE_RULES_FILE = "a4a_rules.json"; private static final String ANGLE_TEMP_RULES = "debug.angle.rules"; private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID"; + private static final String GAME_DRIVER_BLACKLIST_FLAG = "blacklist"; + private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP; private ClassLoader mClassLoader; private String mLayerPath; @@ -362,26 +369,6 @@ public class GraphicsEnvironment { } /** - * Attempt to setup ANGLE with a (temporary) default rules file: b/121153494 - * True: Rules file was loaded. - * False: Rules file was *not* loaded. - */ - private boolean setupAngleRulesDebug(String packageName, String paths, String devOptIn) { - // b/121153494 - // Skip APK rules file checking. - if (!DEBUG) { - Log.v(TAG, "Skipping loading the rules file."); - // Fill in some default values for now, so the loader can get an answer when it asks. - // Most importantly, we need to indicate which app we are init'ing and what the - // developer options for it are so we can turn on ANGLE if needed. - setAngleInfo(paths, packageName, devOptIn, null, 0, 0); - return true; - } - - return false; - } - - /** * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK. * True: APK rules file was loaded. * False: APK rules file was *not* loaded. @@ -418,16 +405,57 @@ public class GraphicsEnvironment { } /** + * Pull ANGLE whitelist from GlobalSettings and compare against current package + */ + private boolean checkAngleWhitelist(Bundle bundle, String packageName) { + List<String> angleWhitelist = + getGlobalSettingsString(bundle, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); + + return angleWhitelist.contains(packageName); + } + + /** * Pass ANGLE details down to trigger enable logic */ public void setupAngle(Context context, Bundle bundle, String packageName) { - String devOptIn = getDriverForPkg(bundle, packageName); + if (packageName.isEmpty()) { + Log.v(TAG, "No package name available yet, skipping ANGLE setup"); + return; + } + String devOptIn = getDriverForPkg(bundle, packageName); if (DEBUG) { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + devOptIn + "'"); } + // We only need to check rules if the app is whitelisted or the developer has + // explicitly chosen something other than default driver. + // + // The whitelist will be generated by the ANGLE APK at both boot time and + // ANGLE update time. It will only include apps mentioned in the rules file. + // + // If the user has set the developer option to something other than default, + // we need to call setupAngleRulesApk() with the package name and the developer + // option value (native/angle/other). Then later when we are actually trying to + // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before + // and can confidently answer yes/no based on the previously set developer + // option value. + boolean whitelisted = checkAngleWhitelist(bundle, packageName); + boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT)); + boolean rulesCheck = (whitelisted || !defaulted); + if (!rulesCheck) { + return; + } + + if (whitelisted) { + Log.v(TAG, "ANGLE whitelist includes " + packageName); + } + if (!defaulted) { + Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn); + } + String anglePkgName = getAnglePackageName(context); if (anglePkgName.isEmpty()) { Log.e(TAG, "Failed to find ANGLE package."); @@ -459,12 +487,6 @@ public class GraphicsEnvironment { return; } - // b/121153494 - if (setupAngleRulesDebug(packageName, paths, devOptIn)) { - // We setup ANGLE with defaults, so we're done here. - return; - } - if (setupAngleRulesApk(anglePkgName, angleInfo, context, packageName, paths, devOptIn)) { // We setup ANGLE with rules from the APK, so we're done here. return; @@ -480,6 +502,24 @@ public class GraphicsEnvironment { return; } + ApplicationInfo driverInfo; + try { + driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName, + PackageManager.MATCH_SYSTEM_ONLY); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); + return; + } + + // O drivers are restricted to the sphal linker namespace, so don't try to use + // packages unless they declare they're compatible with that restriction. + if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) { + if (DEBUG) { + Log.w(TAG, "updated driver package is not known to be compatible with O"); + } + return; + } + // To minimize risk of driver updates crippling the device beyond user repair, never use an // updated driver for privileged or non-updated system apps. Presumably pre-installed apps // were tested thoroughly with the pre-installed driver. @@ -491,7 +531,7 @@ public class GraphicsEnvironment { // GUP_DEV_ALL_APPS // 0: Default (Invalid values fallback to default as well) - // 1: All apps use Game Update Package + // 1: All apps use Game Driver // 2: All apps use system graphics driver int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0); if (gupDevAllApps == 2) { @@ -510,33 +550,45 @@ public class GraphicsEnvironment { } return; } + boolean isDevOptIn = getGlobalSettingsString(coreSettings, + Settings.Global.GUP_DEV_OPT_IN_APPS) + .contains(ai.packageName); - if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS) - .contains(ai.packageName) - && !onWhitelist(context, driverPackageName, ai.packageName)) { + if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) { if (DEBUG) { Log.w(TAG, ai.packageName + " is not on the whitelist."); } return; } - } - ApplicationInfo driverInfo; - try { - driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName, - PackageManager.MATCH_SYSTEM_ONLY); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); - return; - } - - // O drivers are restricted to the sphal linker namespace, so don't try to use - // packages unless they declare they're compatible with that restriction. - if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) { - if (DEBUG) { - Log.w(TAG, "updated driver package is not known to be compatible with O"); + if (!isDevOptIn) { + // At this point, the application is on the whitelist only, check whether it's + // on the blacklist, terminate early when it's on the blacklist. + try { + // TODO(b/121350991) Switch to DeviceConfig with property listener. + String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST); + if (base64String != null && !base64String.isEmpty()) { + Blacklists blacklistsProto = Blacklists.parseFrom( + Base64.decode(base64String, BASE64_FLAGS)); + List<Blacklist> blacklists = blacklistsProto.getBlacklistsList(); + long driverVersionCode = driverInfo.longVersionCode; + for (Blacklist blacklist : blacklists) { + if (blacklist.getVersionCode() == driverVersionCode) { + for (String packageName : blacklist.getPackageNamesList()) { + if (packageName == ai.packageName) { + return; + } + } + break; + } + } + } + } catch (InvalidProtocolBufferException e) { + if (DEBUG) { + Log.w(TAG, "Can't parse blacklist, skip and continue..."); + } + } } - return; } String abi = chooseAbi(driverInfo); diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl index 827170144dea..fe17c6bb14ca 100644 --- a/core/java/android/os/IDeviceIdleController.aidl +++ b/core/java/android/os/IDeviceIdleController.aidl @@ -45,4 +45,6 @@ interface IDeviceIdleController { void exitIdle(String reason); boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener); void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener); + int setPreIdleTimeoutMode(int Mode); + void resetPreIdleTimeoutMode(); } diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 74d434c51781..93d6f4c12128 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -119,6 +119,23 @@ interface IStatsManager { void removeDataFetchOperation(long configKey, in String packageName); /** + * Registers the given pending intent for this packagename. This intent is invoked when the + * active status of any of the configs sent by this package changes and will contain a list of + * config ids that are currently active. It also returns the list of configs that are currently + * active. There can be at most one active configs changed listener per package. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + long[] setActiveConfigsChangedOperation(in IBinder intentSender, in String packageName); + + /** + * Removes the active configs changed operation for the specified package name. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + void removeActiveConfigsChangedOperation(in String packageName); + + /** * Removes the configuration with the matching config key. No-op if this config key does not * exist. * diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 63912ec327a4..d68eeeda2eb9 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -31,6 +31,7 @@ import static android.system.OsConstants.S_ISLNK; import static android.system.OsConstants.S_ISREG; import static android.system.OsConstants.S_IWOTH; +import android.annotation.TestApi; import android.content.BroadcastReceiver; import android.content.ContentProvider; import android.os.MessageQueue.OnFileDescriptorEventListener; @@ -54,6 +55,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InterruptedIOException; +import java.io.UncheckedIOException; import java.net.DatagramSocket; import java.net.Socket; import java.nio.ByteOrder; @@ -393,26 +395,41 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @param socket The Socket whose FileDescriptor is used to create * a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified Socket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromSocket(Socket socket) { FileDescriptor fd = socket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** - * Create a new ParcelFileDescriptor from the specified DatagramSocket. + * Create a new ParcelFileDescriptor from the specified DatagramSocket. The + * new ParcelFileDescriptor holds a dup of the original FileDescriptor in + * the DatagramSocket, so you must still close the DatagramSocket as well + * as the new ParcelFileDescriptor. * * @param datagramSocket The DatagramSocket whose FileDescriptor is used * to create a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified DatagramSocket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) { FileDescriptor fd = datagramSocket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -542,7 +559,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } file.deactivate(); FileDescriptor fd = file.getFileDescriptor(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + return fd != null ? ParcelFileDescriptor.dup(fd) : null; } /** @@ -564,6 +581,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * * @hide */ + @TestApi public static File getFile(FileDescriptor fd) throws IOException { try { final String path = Os.readlink("/proc/self/fd/" + fd.getInt$()); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 0942d97df6c7..7f4254e29aaa 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -336,6 +336,13 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; /** + * User activity event type: {@link android.service.attention.AttentionService} taking action + * on behalf of user. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_ATTENTION = 4; + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when @@ -1724,6 +1731,25 @@ public final class PowerManager { = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED"; /** + * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) . + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0; + + /** + * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode + * longer). + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1; + + /** + * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly) + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2; + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index f2a9adb6feea..d2ab053eb4e6 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -32,16 +32,6 @@ public class Process { private static final String LOG_TAG = "Process"; /** - * @hide for internal use only. - */ - public static final String ZYGOTE_SOCKET = "zygote"; - - /** - * @hide for internal use only. - */ - public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; - - /** * An invalid UID value. */ public static final int INVALID_UID = -1; @@ -479,8 +469,7 @@ public class Process { * State associated with the zygote process. * @hide */ - public static final ZygoteProcess zygoteProcess = - new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET); + public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess(); /** * Start a new process. @@ -538,10 +527,10 @@ public class Process { @Nullable String[] packagesForUid, @Nullable String[] visibleVols, @Nullable String[] zygoteArgs) { - return zygoteProcess.start(processClass, niceName, uid, gid, gids, + return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - packagesForUid, visibleVols, zygoteArgs); + packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs); } /** @hide */ @@ -562,7 +551,7 @@ public class Process { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - packagesForUid, visibleVols, zygoteArgs); + packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs); } /** diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 01d85c6c6c85..99fb608b80dc 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ContentResolver; @@ -25,6 +26,8 @@ import android.hardware.vibrator.V1_2.Effect; import android.net.Uri; import android.util.MathUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -52,26 +55,20 @@ public abstract class VibrationEffect implements Parcelable { * A click effect. * * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_CLICK = Effect.CLICK; /** * A double click effect. * * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; /** * A tick effect. * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_TICK = Effect.TICK; /** @@ -93,9 +90,7 @@ public abstract class VibrationEffect implements Parcelable { /** * A heavy click effect. * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; /** {@hide} */ @@ -136,6 +131,16 @@ public abstract class VibrationEffect implements Parcelable { Effect.RINGTONE_15 }; + /** @hide */ + @IntDef(prefix = { "EFFECT_" }, value = { + EFFECT_TICK, + EFFECT_CLICK, + EFFECT_HEAVY_CLICK, + EFFECT_DOUBLE_CLICK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EffectType {} + /** @hide to prevent subclassing from outside of the framework */ public VibrationEffect() { } @@ -219,6 +224,27 @@ public abstract class VibrationEffect implements Parcelable { } /** + * Create a predefined vibration effect. + * + * Predefined effects are a set of common vibration effects that should be identical, regardless + * of the app they come from, in order to provide a cohesive experience for users across + * the entire device. They also may be custom tailored to the device hardware in order to + * provide a better experience than you could otherwise build using the generic building + * blocks. + * + * This will fallback to a generic pattern if one exists and there does not exist a + * hardware-specific implementation of the effect. + * + * @param effectId The ID of the effect to perform: + * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} + * + * @return The desired effect. + */ + public static VibrationEffect createPrebaked(@EffectType int effectId) { + return get(effectId, true); + } + + /** * Get a predefined vibration effect. * * Predefined effects are a set of common vibration effects that should be identical, regardless diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index ec7782177c73..9e47179e9152 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -62,87 +62,161 @@ import java.util.UUID; * {@hide} */ public class ZygoteProcess { + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SOCKET_NAME = "zygote"; + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary"; + + /** + * @hide for internal use only + */ + public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool"; + + /** + * @hide for internal use only + */ + public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary"; + + /** + * @hide for internal use only + */ private static final String LOG_TAG = "ZygoteProcess"; /** * The name of the socket used to communicate with the primary zygote. */ - private final LocalSocketAddress mSocket; + private final LocalSocketAddress mZygoteSocketAddress; /** * The name of the secondary (alternate ABI) zygote socket. */ - private final LocalSocketAddress mSecondarySocket; + private final LocalSocketAddress mZygoteSecondarySocketAddress; + /** + * The name of the socket used to communicate with the primary blastula pool. + */ + private final LocalSocketAddress mBlastulaPoolSocketAddress; - public ZygoteProcess(String primarySocket, String secondarySocket) { - this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED), - new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED)); + /** + * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool. + */ + private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress; + + public ZygoteProcess() { + mZygoteSocketAddress = + new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); + mZygoteSecondarySocketAddress = + new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); + + mBlastulaPoolSocketAddress = + new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); + mBlastulaPoolSecondarySocketAddress = + new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); } - public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) { - mSocket = primarySocket; - mSecondarySocket = secondarySocket; + public ZygoteProcess(LocalSocketAddress primarySocketAddress, + LocalSocketAddress secondarySocketAddress) { + mZygoteSocketAddress = primarySocketAddress; + mZygoteSecondarySocketAddress = secondarySocketAddress; + + mBlastulaPoolSocketAddress = null; + mBlastulaPoolSecondarySocketAddress = null; } public LocalSocketAddress getPrimarySocketAddress() { - return mSocket; + return mZygoteSocketAddress; } /** * State for communicating with the zygote process. */ public static class ZygoteState { - final LocalSocket socket; - final DataInputStream inputStream; - final BufferedWriter writer; - final List<String> abiList; - - boolean mClosed; - - private ZygoteState(LocalSocket socket, DataInputStream inputStream, - BufferedWriter writer, List<String> abiList) { - this.socket = socket; - this.inputStream = inputStream; - this.writer = writer; - this.abiList = abiList; - } + final LocalSocketAddress mZygoteSocketAddress; + final LocalSocketAddress mBlastulaSocketAddress; + + private final LocalSocket mZygoteSessionSocket; + + final DataInputStream mZygoteInputStream; + final BufferedWriter mZygoteOutputWriter; + + private final List<String> mABIList; + + private boolean mClosed; + + private ZygoteState(LocalSocketAddress zygoteSocketAddress, + LocalSocketAddress blastulaSocketAddress, + LocalSocket zygoteSessionSocket, + DataInputStream zygoteInputStream, + BufferedWriter zygoteOutputWriter, + List<String> abiList) { + this.mZygoteSocketAddress = zygoteSocketAddress; + this.mBlastulaSocketAddress = blastulaSocketAddress; + this.mZygoteSessionSocket = zygoteSessionSocket; + this.mZygoteInputStream = zygoteInputStream; + this.mZygoteOutputWriter = zygoteOutputWriter; + this.mABIList = abiList; + } + + /** + * Create a new ZygoteState object by connecting to the given Zygote socket and saving the + * given blastula socket address. + * + * @param zygoteSocketAddress Zygote socket to connect to + * @param blastulaSocketAddress Blastula socket address to save for later + * @return A new ZygoteState object containing a session socket for the given Zygote socket + * address + * @throws IOException + */ + public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress, + LocalSocketAddress blastulaSocketAddress) + throws IOException { - public static ZygoteState connect(LocalSocketAddress address) throws IOException { DataInputStream zygoteInputStream = null; - BufferedWriter zygoteWriter = null; - final LocalSocket zygoteSocket = new LocalSocket(); + BufferedWriter zygoteOutputWriter = null; + final LocalSocket zygoteSessionSocket = new LocalSocket(); try { - zygoteSocket.connect(address); - - zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream()); - - zygoteWriter = new BufferedWriter(new OutputStreamWriter( - zygoteSocket.getOutputStream()), 256); + zygoteSessionSocket.connect(zygoteSocketAddress); + zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream()); + zygoteOutputWriter = + new BufferedWriter( + new OutputStreamWriter(zygoteSessionSocket.getOutputStream()), + Zygote.SOCKET_BUFFER_SIZE); } catch (IOException ex) { try { - zygoteSocket.close(); - } catch (IOException ignore) { - } + zygoteSessionSocket.close(); + } catch (IOException ignore) { } throw ex; } - String abiListString = getAbiList(zygoteWriter, zygoteInputStream); - Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/" - + address.getName() + " opened, supported ABIS: " + abiListString); + return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress, + zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter, + getAbiList(zygoteOutputWriter, zygoteInputStream)); + } - return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, - Arrays.asList(abiListString.split(","))); + LocalSocket getBlastulaSessionSocket() throws IOException { + final LocalSocket blastulaSessionSocket = new LocalSocket(); + blastulaSessionSocket.connect(this.mBlastulaSocketAddress); + + return blastulaSessionSocket; } boolean matches(String abi) { - return abiList.contains(abi); + return mABIList.contains(abi); } public void close() { try { - socket.close(); + mZygoteSessionSocket.close(); } catch (IOException ex) { Log.e(LOG_TAG,"I/O exception on routine close", ex); } @@ -237,12 +311,13 @@ public class ZygoteProcess { @Nullable String packageName, @Nullable String[] packagesForUid, @Nullable String[] visibleVols, + boolean useBlastulaPool, @Nullable String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, - packageName, packagesForUid, visibleVols, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false, + packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -260,7 +335,7 @@ public class ZygoteProcess { * @throws ZygoteStartFailedEx if the query failed. */ @GuardedBy("mLock") - private static String getAbiList(BufferedWriter writer, DataInputStream inputStream) + private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream) throws IOException { // Each query starts with the argument count (1 in this case) writer.write("1"); @@ -276,7 +351,9 @@ public class ZygoteProcess { byte[] bytes = new byte[numBytes]; inputStream.readFully(bytes); - return new String(bytes, StandardCharsets.US_ASCII); + String rawList = new String(bytes, StandardCharsets.US_ASCII); + + return Arrays.asList(rawList.split(",")); } /** @@ -288,59 +365,128 @@ public class ZygoteProcess { */ @GuardedBy("mLock") private static Process.ProcessStartResult zygoteSendArgsAndGetResult( - ZygoteState zygoteState, ArrayList<String> args) + ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args) throws ZygoteStartFailedEx { - try { - // Throw early if any of the arguments are malformed. This means we can - // avoid writing a partial response to the zygote. - int sz = args.size(); - for (int i = 0; i < sz; i++) { - if (args.get(i).indexOf('\n') >= 0) { - throw new ZygoteStartFailedEx("embedded newlines not allowed"); - } + // Throw early if any of the arguments are malformed. This means we can + // avoid writing a partial response to the zygote. + for (String arg : args) { + if (arg.indexOf('\n') >= 0) { + throw new ZygoteStartFailedEx("embedded newlines not allowed"); } + } - /** - * See com.android.internal.os.SystemZygoteInit.readArgumentList() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * the child or -1 on failure, followed by boolean to - * indicate whether a wrapper process was used. - */ - final BufferedWriter writer = zygoteState.writer; - final DataInputStream inputStream = zygoteState.inputStream; - - writer.write(Integer.toString(args.size())); - writer.newLine(); + /** + * See com.android.internal.os.SystemZygoteInit.readArgumentList() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure, followed by boolean to + * indicate whether a wrapper process was used. + */ + String msgStr = Integer.toString(args.size()) + "\n" + + String.join("\n", args) + "\n"; - for (int i = 0; i < sz; i++) { - String arg = args.get(i); - writer.write(arg); - writer.newLine(); + // Should there be a timeout on this? + Process.ProcessStartResult result = new Process.ProcessStartResult(); + + // TODO (chriswailes): Move branch body into separate function. + if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) { + LocalSocket blastulaSessionSocket = null; + + try { + blastulaSessionSocket = zygoteState.getBlastulaSessionSocket(); + + final BufferedWriter blastulaWriter = + new BufferedWriter( + new OutputStreamWriter(blastulaSessionSocket.getOutputStream()), + Zygote.SOCKET_BUFFER_SIZE); + final DataInputStream blastulaReader = + new DataInputStream(blastulaSessionSocket.getInputStream()); + + blastulaWriter.write(msgStr); + blastulaWriter.flush(); + + result.pid = blastulaReader.readInt(); + // Blastulas can't be used to spawn processes that need wrappers. + result.usingWrapper = false; + + if (result.pid < 0) { + throw new ZygoteStartFailedEx("Blastula specialization failed"); + } + + return result; + } catch (IOException ex) { + // If there was an IOException using the blastula pool we will log the error and + // attempt to start the process through the Zygote. + Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - " + + ex.toString()); + } finally { + try { + blastulaSessionSocket.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage()); + } } + } - writer.flush(); + try { + final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter; + final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream; - // Should there be a timeout on this? - Process.ProcessStartResult result = new Process.ProcessStartResult(); + zygoteWriter.write(msgStr); + zygoteWriter.flush(); // Always read the entire result from the input stream to avoid leaving // bytes in the stream for future process starts to accidentally stumble // upon. - result.pid = inputStream.readInt(); - result.usingWrapper = inputStream.readBoolean(); - - if (result.pid < 0) { - throw new ZygoteStartFailedEx("fork() failed"); - } - return result; + result.pid = zygoteInputStream.readInt(); + result.usingWrapper = zygoteInputStream.readBoolean(); } catch (IOException ex) { zygoteState.close(); + Log.e(LOG_TAG, "IO Exception while communicating with Zygote - " + + ex.toString()); throw new ZygoteStartFailedEx(ex); } + + if (result.pid < 0) { + throw new ZygoteStartFailedEx("fork() failed"); + } + + return result; + } + + /** + * Flags that may not be passed to a blastula. + */ + private static final String[] INVALID_BLASTULA_FLAGS = { + "--query-abi-list", + "--get-pid", + "--preload-default", + "--preload-package", + "--preload-app", + "--start-child-zygote", + "--set-api-blacklist-exemptions", + "--hidden-api-log-sampling-rate", + "--invoke-with" + }; + + /** + * Tests a command list to see if it is valid to send to a blastula. + * @param args Zygote/Blastula command arguments + * @return True if the command can be passed to a blastula; false otherwise + */ + private static boolean isValidBlastulaCommand(ArrayList<String> args) { + for (String flag : args) { + for (String badFlag : INVALID_BLASTULA_FLAGS) { + if (flag.startsWith(badFlag)) { + return false; + } + } + } + + return true; } /** @@ -382,6 +528,7 @@ public class ZygoteProcess { @Nullable String packageName, @Nullable String[] packagesForUid, @Nullable String[] visibleVols, + boolean useBlastulaPool, @Nullable String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -488,7 +635,9 @@ public class ZygoteProcess { } synchronized(mLock) { - return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); + return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), + useBlastulaPool, + argsForZygote); } } @@ -528,18 +677,18 @@ public class ZygoteProcess { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); + state.mZygoteOutputWriter.write("1"); // ... followed by a new-line. - state.writer.newLine(); + state.mZygoteOutputWriter.newLine(); // ... followed by our only argument. - state.writer.write("--get-pid"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("--get-pid"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); // The response is a length prefixed stream of ASCII bytes. - int numBytes = state.inputStream.readInt(); + int numBytes = state.mZygoteInputStream.readInt(); byte[] bytes = new byte[numBytes]; - state.inputStream.readFully(bytes); + state.mZygoteInputStream.readFully(bytes); return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII)); } @@ -593,16 +742,16 @@ public class ZygoteProcess { return true; } try { - state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); - state.writer.newLine(); - state.writer.write("--set-api-blacklist-exemptions"); - state.writer.newLine(); + state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions"); + state.mZygoteOutputWriter.newLine(); for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) { - state.writer.write(mApiBlacklistExemptions.get(i)); - state.writer.newLine(); + state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i)); + state.mZygoteOutputWriter.newLine(); } - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status); } @@ -622,13 +771,13 @@ public class ZygoteProcess { return; } try { - state.writer.write(Integer.toString(1)); - state.writer.newLine(); - state.writer.write("--hidden-api-log-sampling-rate=" + state.mZygoteOutputWriter.write(Integer.toString(1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate=" + Integer.toString(mHiddenApiAccessLogSampleRate)); - state.writer.newLine(); - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status); } @@ -638,22 +787,29 @@ public class ZygoteProcess { } /** - * Tries to open socket to Zygote process if not already open. If - * already open, does nothing. May block and retry. Requires that mLock be held. + * Tries to open a session socket to a Zygote process with a compatible ABI if one is not + * already open. If a compatible session socket is already open that session socket is returned. + * This function may block and may have to try connecting to multiple Zygotes to find the + * appropriate one. Requires that mLock be held. */ @GuardedBy("mLock") - private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { + private ZygoteState openZygoteSocketIfNeeded(String abi) + throws ZygoteStartFailedEx { + Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); if (primaryZygoteState == null || primaryZygoteState.isClosed()) { try { - primaryZygoteState = ZygoteState.connect(mSocket); + primaryZygoteState = + ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } + maybeSetApiBlacklistExemptions(primaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState); } + if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } @@ -661,10 +817,13 @@ public class ZygoteProcess { // The primary zygote didn't match. Try the secondary. if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { try { - secondaryZygoteState = ZygoteState.connect(mSecondarySocket); + secondaryZygoteState = + ZygoteState.connect(mZygoteSecondarySocketAddress, + mBlastulaPoolSecondarySocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } + maybeSetApiBlacklistExemptions(secondaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState); } @@ -685,11 +844,11 @@ public class ZygoteProcess { IOException { synchronized (mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); - state.writer.write("2"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("2"); + state.mZygoteOutputWriter.newLine(); - state.writer.write("--preload-app"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("--preload-app"); + state.mZygoteOutputWriter.newLine(); // Zygote args needs to be strings, so in order to pass ApplicationInfo, // write it to a Parcel, and base64 the raw Parcel bytes to the other side. @@ -697,12 +856,12 @@ public class ZygoteProcess { appInfo.writeToParcel(parcel, 0 /* flags */); String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall()); parcel.recycle(); - state.writer.write(encodedParcelData); - state.writer.newLine(); + state.mZygoteOutputWriter.write(encodedParcelData); + state.mZygoteOutputWriter.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -715,27 +874,27 @@ public class ZygoteProcess { IOException { synchronized(mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); - state.writer.write("5"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("5"); + state.mZygoteOutputWriter.newLine(); - state.writer.write("--preload-package"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("--preload-package"); + state.mZygoteOutputWriter.newLine(); - state.writer.write(packagePath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(packagePath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libsPath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libsPath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libFileName); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libFileName); + state.mZygoteOutputWriter.newLine(); - state.writer.write(cacheKey); - state.writer.newLine(); + state.mZygoteOutputWriter.write(cacheKey); + state.mZygoteOutputWriter.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -749,13 +908,13 @@ public class ZygoteProcess { synchronized (mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); - state.writer.newLine(); - state.writer.write("--preload-default"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("1"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--preload-default"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -763,20 +922,21 @@ public class ZygoteProcess { * Try connecting to the Zygote over and over again until we hit a time-out. * @param socketName The name of the socket to connect to. */ - public static void waitForConnectionToZygote(String socketName) { - final LocalSocketAddress address = - new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED); - waitForConnectionToZygote(address); + public static void waitForConnectionToZygote(String zygoteSocketName) { + final LocalSocketAddress zygoteSocketAddress = + new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED); + waitForConnectionToZygote(zygoteSocketAddress); } /** * Try connecting to the Zygote over and over again until we hit a time-out. * @param address The name of the socket to connect to. */ - public static void waitForConnectionToZygote(LocalSocketAddress address) { + public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) { for (int n = 20; n >= 0; n--) { try { - final ZygoteState zs = ZygoteState.connect(address); + final ZygoteState zs = + ZygoteState.connect(zygoteSocketAddress, null); zs.close(); return; } catch (IOException ioe) { @@ -789,7 +949,8 @@ public class ZygoteProcess { } catch (InterruptedException ie) { } } - Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName()); + Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + + zygoteSocketAddress.getName()); } /** @@ -839,7 +1000,8 @@ public class ZygoteProcess { gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, true /* startChildZygote */, null /* packageName */, - null /* packagesForUid */, null /* visibleVolumes */, extraArgs); + null /* packagesForUid */, null /* visibleVolumes */, + false /* useBlastulaPool */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 249b622dac4d..5dd869f46b4e 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -35,4 +35,6 @@ oneway interface IPermissionController { void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted, boolean countSystem, in RemoteCallback callback); void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback); + void isApplicationQualifiedForRole(String roleName, String packageName, + in RemoteCallback callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index bfcca7c10151..b59d0c7a660e 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -21,6 +21,7 @@ import static android.permission.PermissionControllerService.SERVICE_INTERFACE; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.Manifest; import android.annotation.CallbackExecutor; @@ -343,6 +344,28 @@ public final class PermissionControllerManager { } /** + * Check whether an application is qualified for a role. + * + * @param roleName name of the role to check for + * @param packageName package name of the application to check for + * @param executor Executor on which to invoke the callback + * @param callback Callback to receive the result + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + checkStringNotEmpty(roleName); + checkStringNotEmpty(packageName); + checkNotNull(executor); + checkNotNull(callback); + + sRemoteService.scheduleRequest(new PendingIsApplicationQualifiedForRoleRequest( + sRemoteService, roleName, packageName, executor, callback)); + } + + /** * A connection to the remote service */ static final class RemoteService extends @@ -810,4 +833,58 @@ public final class PermissionControllerManager { } } } + + /** + * Request for {@link #isApplicationQualifiedForRole}. + */ + private static final class PendingIsApplicationQualifiedForRoleRequest extends + AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> { + + private final @NonNull String mRoleName; + private final @NonNull String mPackageName; + private final @NonNull Consumer<Boolean> mCallback; + + private final @NonNull RemoteCallback mRemoteCallback; + + private PendingIsApplicationQualifiedForRoleRequest(@NonNull RemoteService service, + @NonNull String roleName, @NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + super(service); + + mRoleName = roleName; + mPackageName = packageName; + mCallback = callback; + + mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { + long token = Binder.clearCallingIdentity(); + try { + boolean qualified; + if (result != null) { + qualified = result.getBoolean(KEY_RESULT); + } else { + qualified = false; + } + callback.accept(qualified); + } finally { + Binder.restoreCallingIdentity(token); + finish(); + } + }), null); + } + + @Override + protected void onTimeout(RemoteService remoteService) { + mCallback.accept(false); + } + + @Override + public void run() { + try { + getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName, + mPackageName, mRemoteCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error checking whether application qualifies for role", e); + } + } + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 10e8c8d5b8dd..9a58b971baf5 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.Preconditions.checkStringNotEmpty; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -136,8 +137,19 @@ public abstract class PermissionControllerService extends Service { * * @return descriptions of the users of permissions */ - public abstract @NonNull List<RuntimePermissionUsageInfo> - onPermissionUsageResult(boolean countSystem, long numMillis); + public abstract @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages( + boolean countSystem, long numMillis); + + /** + * Check whether an application is qualified for a role. + * + * @param roleName name of the role to check for + * @param packageName package name of the application to check for + * + * @return whether the application is qualified for the role. + */ + public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName, + @NonNull String packageName); @Override public final IBinder onBind(Intent intent) { @@ -240,6 +252,20 @@ public abstract class PermissionControllerService extends Service { PermissionControllerService.this, countSystem, numMillis, callback)); } + + @Override + public void isApplicationQualifiedForRole(String roleName, String packageName, + RemoteCallback callback) { + checkStringNotEmpty(roleName); + checkStringNotEmpty(packageName); + checkNotNull(callback, "callback"); + + enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null); + + mHandler.sendMessage(obtainMessage( + PermissionControllerService::isApplicationQualifiedForRole, + PermissionControllerService.this, roleName, packageName, callback)); + } }; } @@ -296,7 +322,7 @@ public abstract class PermissionControllerService extends Service { private void getPermissionUsages(boolean countSystem, long numMillis, @NonNull RemoteCallback callback) { List<RuntimePermissionUsageInfo> users = - onPermissionUsageResult(countSystem, numMillis); + onGetPermissionUsages(countSystem, numMillis); if (users != null && !users.isEmpty()) { Bundle result = new Bundle(); result.putParcelableList(PermissionControllerManager.KEY_RESULT, users); @@ -305,4 +331,12 @@ public abstract class PermissionControllerService extends Service { callback.sendResult(null); } } + + private void isApplicationQualifiedForRole(@NonNull String roleName, + @NonNull String packageName, @NonNull RemoteCallback callback) { + boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName); + Bundle result = new Bundle(); + result.putBoolean(PermissionControllerManager.KEY_RESULT, qualified); + callback.sendResult(result); + } } diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java index 8d568c84b915..33795f81ded9 100644 --- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java +++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java @@ -78,15 +78,6 @@ public abstract class RuntimePermissionPresenterService extends Service { public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions( @NonNull String packageName); - /** - * Revokes the permission {@code permissionName} for app {@code packageName} - * - * @param packageName The package for which to revoke - * @param permissionName The permission to revoke - */ - public abstract void onRevokeRuntimePermission(@NonNull String packageName, - @NonNull String permissionName); - @Override public final IBinder onBind(Intent intent) { return new IRuntimePermissionPresenter.Stub() { @@ -99,17 +90,6 @@ public abstract class RuntimePermissionPresenterService extends Service { obtainMessage(RuntimePermissionPresenterService::getAppPermissions, RuntimePermissionPresenterService.this, packageName, callback)); } - - @Override - public void revokeRuntimePermission(String packageName, String permissionName) { - checkNotNull(packageName, "packageName"); - checkNotNull(permissionName, "permissionName"); - - mHandler.sendMessage( - obtainMessage(RuntimePermissionPresenterService::onRevokeRuntimePermission, - RuntimePermissionPresenterService.this, packageName, - permissionName)); - } }; } diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index c167ea18f0c5..8a52f1f0eec0 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -19,6 +19,7 @@ package android.provider; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AlarmManager; @@ -44,6 +45,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import java.util.Set; + /** * <p> * The contract between the calendar provider and applications. Contains @@ -217,7 +220,7 @@ public final class CalendarContract { * The intent will have its action set to * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras * corresponding to the API's arguments. A calendar app intending to support - * cross profile events viewing should handle this intent, parse the arguments + * cross-profile events viewing should handle this intent, parse the arguments * and show the appropriate UI. * * @param context the context. @@ -767,9 +770,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is * queried from a package that is not whitelisted by profile owner of the managed profile - * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * via + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -802,6 +806,7 @@ public final class CalendarContract { * * @hide */ + @TestApi public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { ACCOUNT_NAME, ACCOUNT_TYPE, @@ -1758,9 +1763,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is * queried from a package that is not whitelisted by profile owner of the managed profile - * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * via + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -1828,6 +1834,7 @@ public final class CalendarContract { * * @hide */ + @TestApi public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { _SYNC_ID, DIRTY, @@ -1968,10 +1975,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar for the managed profile is disabled in + * of a managed profile, or cross-profile calendar for the managed profile is disabled in * Settings, or this uri is queried from a package that is not whitelisted by * profile owner of the managed profile via - * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 25554b937065..81e1eb99336b 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -126,6 +126,7 @@ public final class ContactsContract { * Prefix for column names that are not visible to client apps. * @hide */ + @TestApi public static final String HIDDEN_COLUMN_PREFIX = "x_"; /** @@ -8444,6 +8445,7 @@ public final class ContactsContract { * nothing will be done. * @hide */ + @TestApi public static final String UNDEMOTE_METHOD = "undemote"; /** diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index cd3efb495034..148dd910ef69 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -54,6 +54,7 @@ public final class DeviceConfig { /** * Namespace for all Game Driver features. + * * @hide */ @SystemApi @@ -103,6 +104,100 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; + /** + * Namespace for attention-based features provided by on-device machine intelligence. + * + * @hide + */ + @SystemApi + public interface IntelligenceAttention { + String NAMESPACE = "intelligence_attention"; + /** + * If {@code true}, enables the attention check. + */ + String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled"; + /** + * Settings for performing the attention check. + */ + String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings"; + } + + /** + * Telephony related properties definitions. + * + * @hide + */ + @SystemApi + public interface Telephony { + String NAMESPACE = "telephony"; + /** + * Whether to apply ramping ringer on incoming phone calls. + */ + String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer"; + /** + * Ringer ramping time in milliseconds. + */ + String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration"; + } + + /** + * Namespace for Full Stack Integrity to run privileged apps only in JIT mode. The flag applies + * at process start, so reboot is a way to bring the device to a clean state. + * + * @hide + */ + @SystemApi + public interface FsiBoot { + String NAMESPACE = "fsi_boot"; + String OOB_ENABLED = "oob_enabled"; + String OOB_WHITELIST = "oob_whitelist"; + } + + /** + * Namespace for activity manager related features. These features will be applied + * immediately upon change. + * + * @hide + */ + @SystemApi + public interface ActivityManager { + String NAMESPACE = "activity_manager"; + + /** + * App compaction flags. See {@link com.android.server.am.AppCompactor}. + */ + String KEY_USE_COMPACTION = "use_compaction"; + String KEY_COMPACT_ACTION_1 = "compact_action_1"; + String KEY_COMPACT_ACTION_2 = "compact_action_2"; + String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + + /** + * Maximum number of cached processes. See + * {@link com.android.server.am.ActivityManagerConstants}. + */ + String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + } + + /** + * Namespace for storage-related features. + * + * @hide + */ + @SystemApi + public interface Storage { + String NAMESPACE = "storage"; + + /** + * If {@code 1}, enables the isolated storage feature. If {@code -1}, + * disables the isolated storage feature. If {@code 0}, uses the default + * value from the build system. + */ + String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled"; + } + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners = @@ -118,9 +213,8 @@ public final class DeviceConfig { * Look up the value of a property for a particular namespace. * * @param namespace The namespace containing the property to look up. - * @param name The name of the property to look up. + * @param name The name of the property to look up. * @return the corresponding value, or null if not present. - * * @hide */ @SystemApi @@ -142,14 +236,13 @@ public final class DeviceConfig { * All properties stored for a particular scope can be reverted to their default values * by passing the namespace to {@link #resetToDefaults(int, String)}. * - * @param namespace The namespace containing the property to create or update. - * @param name The name of the property to create or update. - * @param value The value to store for the property. + * @param namespace The namespace containing the property to create or update. + * @param name The name of the property to create or update. + * @param value The value to store for the property. * @param makeDefault Whether to make the new value the default one. * @return True if the value was set, false if the storage implementation throws errors. - * @see #resetToDefaults(int, String). - * * @hide + * @see #resetToDefaults(int, String). */ @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) @@ -168,9 +261,8 @@ public final class DeviceConfig { * * @param resetMode The reset mode to use. * @param namespace Optionally, the specific namespace which resets will be limited to. - * @see #setProperty(String, String, String, boolean) - * * @hide + * @see #setProperty(String, String, String, boolean) */ @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) @@ -187,12 +279,11 @@ public final class DeviceConfig { * will replace the old namespace and executor. Remove the listener entirely by calling * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}. * - * @param namespace The namespace containing properties to monitor. - * @param executor The executor which will be used to run callbacks. + * @param namespace The namespace containing properties to monitor. + * @param executor The executor which will be used to run callbacks. * @param onPropertyChangedListener The listener to add. - * @see #removeOnPropertyChangedListener(OnPropertyChangedListener) - * * @hide + * @see #removeOnPropertyChangedListener(OnPropertyChangedListener) */ @SystemApi @RequiresPermission(READ_DEVICE_CONFIG) @@ -224,9 +315,8 @@ public final class DeviceConfig { * property changes. * * @param onPropertyChangedListener The listener to remove. - * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener) - * * @hide + * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener) */ @SystemApi public static void removeOnPropertyChangedListener( @@ -327,8 +417,8 @@ public final class DeviceConfig { * Called when a property has changed. * * @param namespace The namespace containing the property which has changed. - * @param name The name of the property which has changed. - * @param value The new value of the property which has changed. + * @param name The name of the property which has changed. + * @param value The new value of the property which has changed. */ void onPropertyChanged(String namespace, String name, String value); } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 487198ba4d45..887175a80421 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -89,11 +89,26 @@ public final class MediaStore { /** A content:// style uri to the authority for the media provider */ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); - /** {@hide} */ + /** + * Volume name used for content on "internal" storage of device. This + * volume contains media distributed with the device, such as built-in + * ringtones and wallpapers. + */ public static final String VOLUME_INTERNAL = "internal"; - /** {@hide} */ + + /** + * Volume name used for content on "external" storage of device. This only + * includes media on the primary shared storage device; the contents of any + * secondary storage devices can be obtained using + * {@link #getAllVolumeNames(Context)}. + */ public static final String VOLUME_EXTERNAL = "external"; + /** {@hide} */ @TestApi + public static final String SCAN_FILE_CALL = "scan_file"; + /** {@hide} */ @TestApi + public static final String SCAN_VOLUME_CALL = "scan_volume"; + /** * The method name used by the media scanner and mtp to tell the media provider to * rescan and reclassify that have become unhidden because of renaming folders or @@ -1566,7 +1581,13 @@ public final class MediaStore { /** * This class provides utility methods to obtain thumbnails for various * {@link Images} items. + * + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it offers + * richer control over requested thumbnail sizes and + * cancellation behavior. */ + @Deprecated public static class Thumbnails implements BaseColumns { public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); @@ -2743,7 +2764,13 @@ public final class MediaStore { /** * This class provides utility methods to obtain thumbnails for various * {@link Video} items. + * + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it offers + * richer control over requested thumbnail sizes and + * cancellation behavior. */ + @Deprecated public static class Thumbnails implements BaseColumns { /** * Cancel any outstanding {@link #getThumbnail} requests, causing @@ -2970,6 +2997,7 @@ public final class MediaStore { * * @hide */ + @TestApi public static @NonNull File getVolumePath(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { @@ -3000,6 +3028,7 @@ public final class MediaStore { * * @hide */ + @TestApi public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 707778fc5b83..43e68e537ff5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -59,6 +59,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; +import android.hardware.display.ColorDisplayManager; import android.location.LocationManager; import android.media.AudioFormat; import android.net.ConnectivityManager; @@ -89,7 +90,6 @@ import android.util.MemoryIntArray; import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.ColorDisplayController; import com.android.internal.widget.ILockSettings; import java.io.IOException; @@ -3239,8 +3239,8 @@ public final class Settings { private static final Validator DISPLAY_COLOR_MODE_VALIDATOR = new SettingsValidators.InclusiveIntegerRangeValidator( - ColorDisplayController.COLOR_MODE_NATURAL, - ColorDisplayController.COLOR_MODE_AUTOMATIC); + ColorDisplayManager.COLOR_MODE_NATURAL, + ColorDisplayManager.COLOR_MODE_AUTOMATIC); /** * The amount of time in milliseconds before the device goes to sleep or begins @@ -7802,6 +7802,9 @@ public final class Settings { * or an activity that handles ACTION_ASSIST, or empty which means using the default * handling. * + * <p>This should be set indirectly by setting the {@link + * android.app.role.RoleManager#ROLE_ASSISTANT assistant role}. + * * @hide */ @UnsupportedAppUsage @@ -8236,6 +8239,16 @@ public final class Settings { private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR; /** + * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right + * swipe). + * + * @hide + */ + public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl"; + + private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR; + + /** * Comma separated list of QS tiles that have been auto-added already. * @hide */ @@ -8421,6 +8434,27 @@ public final class Settings { public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; + + /** + * Comma separated list of enabled overlay packages for all android.theme.customization.* + * categories. If there is no corresponding package included for a category, then all + * overlay packages in that category must be disabled. + * @hide + */ + @SystemApi + public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = + "theme_customization_overlay_packages"; + + private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR = + new SettingsValidators.PackageNameListValidator(","); + + /** + * Controls whether aware is enabled. + * @hide + */ + public static final String AWARE_ENABLED = "aware_enabled"; + + private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; /** * This are the settings to be backed up. * @@ -8516,6 +8550,7 @@ public final class Settings { ASSIST_GESTURE_WAKE_ENABLED, VR_DISPLAY_MODE, NOTIFICATION_BADGING, + NOTIFICATION_DISMISS_RTL, QS_AUTO_ADDED_TILES, SCREENSAVER_ENABLED, SCREENSAVER_COMPONENTS, @@ -8544,6 +8579,8 @@ public final class Settings { LOCK_SCREEN_WHEN_TRUST_LOST, SKIP_GESTURE, SILENCE_GESTURE, + THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + AWARE_ENABLED, }; /** @@ -8676,6 +8713,7 @@ public final class Settings { VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR); VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR); VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR); + VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR); VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR); VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR); VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR); @@ -8714,6 +8752,9 @@ public final class Settings { VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR); VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR); VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR); + VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR); + VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR); } /** @@ -8745,6 +8786,7 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED); + CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD); if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD); CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); @@ -9452,23 +9494,6 @@ public final class Settings { "hdmi_control_auto_device_off_enabled"; /** - * If <b>true</b>, enables out-of-the-box execution for priv apps. - * Default: false - * Values: 0 = false, 1 = true - * - * @hide - */ - public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled"; - - /** - * Comma separated list of privileged package names, which will be running out-of-box APK. - * Default: "ALL" - * - * @hide - */ - public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list"; - - /** * The interval in milliseconds at which location requests will be throttled when they are * coming from the background. * @@ -9493,6 +9518,14 @@ public final class Settings { "location_background_throttle_package_whitelist"; /** + * Packages that are whitelisted for ignoring location settings (may retrieve location even + * when user location settings are off), for emergency purposes. + * @hide + */ + public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = + "location_ignore_settings_package_whitelist"; + + /** * Whether to disable location status callbacks in preparation for deprecation. * @hide */ @@ -11244,8 +11277,6 @@ public final class Settings { * service_max_inactivity (long) * service_bg_start_timeout (long) * process_start_async (boolean) - * use_mem_aware_service_restarts (boolean) - * service_restart_delay_duration (long) * </pre> * * <p> @@ -11508,6 +11539,20 @@ public final class Settings { public static final String DISPLAY_PANEL_LPM = "display_panel_lpm"; /** + * App time limit usage source setting. + * This controls which app in a task will be considered the source of usage when + * calculating app usage time limits. + * + * 1 -> task root app + * 2 -> current app + * Any other value defaults to task root app. + * + * Need to reboot the device for this setting to take effect. + * @hide + */ + public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source"; + + /** * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: * <p> @@ -11869,6 +11914,28 @@ public final class Settings { public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background"; /** + * The default time in ms within which a subsequent connection from an always allowed system + * is allowed to reconnect without user interaction. + * + * @hide + */ + public static final long DEFAULT_ADB_ALLOWED_CONNECTION_TIME = 604800000; + + /** + * When the user first connects their device to a system a prompt is displayed to allow + * the adb connection with an option to 'Always allow' connections from this system. If the + * user selects this always allow option then the connection time is stored for the system. + * This setting is the time in ms within which a subsequent connection from an always + * allowed system is allowed to reconnect without user interaction. + * + * Type: long + * + * @hide + */ + public static final String ADB_ALLOWED_CONNECTION_TIME = + "adb_allowed_connection_time"; + + /** * Get the key that retrieves a bluetooth headset's priority. * @hide */ @@ -12087,6 +12154,13 @@ public final class Settings { "angle_gl_driver_selection_values"; /** + * List of package names that should check ANGLE rules + * @hide + */ + public static final String GLOBAL_SETTINGS_ANGLE_WHITELIST = + "angle_whitelist"; + + /** * Game Update Package global preference for all Apps. * 0 = Default * 1 = All Apps use Game Update Package @@ -12116,6 +12190,14 @@ public final class Settings { public static final String GUP_BLACKLIST = "gup_blacklist"; /** + * Apps on the whitelist that are allowed to use Game Driver. + * The string is a list of application package names, seperated by comma. + * i.e. <apk1>,<apk2>,...,<apkN> + * @hide + */ + public static final String GAME_DRIVER_WHITELIST = "game_driver_whitelist"; + + /** * Ordered GPU debug layer list for Vulkan * i.e. <layer1>:<layer2>:...:<layerN> * @hide @@ -12160,6 +12242,31 @@ public final class Settings { public static final String LOW_POWER_MODE_STICKY = "low_power_sticky"; /** + * When a device is unplugged from a changer (or is rebooted), do not re-activate battery + * saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or + * above this threshold. + * + * @hide + */ + public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL = + "low_power_sticky_auto_disable_level"; + + private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR = + new SettingsValidators.InclusiveIntegerRangeValidator(0, 100); + + /** + * Whether sticky battery saver should be deactivated once the battery level has reached the + * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}. + * + * @hide + */ + public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED = + "low_power_sticky_auto_disable_enabled"; + + private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"}); + + /** * Battery level [1-100] at which low power mode automatically turns on. * Pre-Q If 0, it will not automatically turn on. Q and newer it will only automatically * turn on if the {@link #AUTOMATIC_POWER_SAVER_MODE} setting is also set to @@ -12171,7 +12278,6 @@ public final class Settings { */ public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level"; - private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR = new SettingsValidators.InclusiveIntegerRangeValidator(0, 100); @@ -12925,48 +13031,37 @@ public final class Settings { "sms_access_restriction_enabled"; /** - * If set to 1, an app must have the READ_PRIVILEGED_PHONE_STATE permission (or be a device - * / profile owner with the READ_PHONE_STATE permission) to access device identifiers. - * - * STOPSHIP: Remove this once we ship with the new device identifier check enabled. - * - * @hide - */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED = - "privileged_device_identifier_check_enabled"; - - /** - * If set to 1, an app that is targeting Q and does not meet the new requirements to access - * device identifiers will receive a SecurityException. + * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE + * permission check for 3P apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED = - "privileged_device_identifier_target_q_behavior_enabled"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED = + "privileged_device_identifier_3p_check_relaxed"; /** * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE - * permission check for 3P apps. + * permission check for preloaded non-privileged apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED = - "privileged_device_identifier_3p_check_relaxed"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED = + "privileged_device_identifier_non_priv_check_relaxed"; /** * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE - * permission check for preloaded non-privileged apps. + * permission check for preloaded privileged apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED = - "privileged_device_identifier_non_priv_check_relaxed"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED = + "privileged_device_identifier_priv_check_relaxed"; /** * If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored @@ -13162,6 +13257,8 @@ public final class Settings { ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, LOW_POWER_MODE_TRIGGER_LEVEL, + LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, + LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, BLUETOOTH_ON, PRIVATE_DNS_MODE, PRIVATE_DNS_SPECIFIER, @@ -13200,6 +13297,10 @@ public final class Settings { VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR); VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR); + VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, + LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR); + VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, + LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR); VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR); VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR); @@ -13714,6 +13815,19 @@ public final class Settings { "user_preferred_sub2","user_preferred_sub3"}; /** + * Which subscription is enabled for a physical slot. + * @hide + */ + public static final String ENABLED_SUBSCRIPTION_FOR_SLOT = "enabled_subscription_for_slot"; + + /** + * Whether corresponding logical modem is enabled for a physical slot. + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String MODEM_STACK_ENABLED_FOR_SLOT = "modem_stack_enabled_for_slot"; + + /** * Whether to enable new contacts aggregator or not. * The value 1 - enable, 0 - disable * @hide @@ -14167,6 +14281,17 @@ public final class Settings { */ public static final String APPOP_HISTORY_PARAMETERS = "appop_history_parameters"; + + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * This is used as an override for constants defined in BatteryStatsImpl for + * ease of experimentation. + * + * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS + * @hide + */ + public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY = + "battery_charging_state_update_delay"; } /** @@ -14490,6 +14615,17 @@ public final class Settings { "android.settings.panel.action.INTERNET_CONNECTIVITY"; /** + * Activity Action: Show a settings dialog containing NFC-related settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NFC = + "android.settings.panel.action.NFC"; + + /** * Activity Action: Show a settings dialog containing all volume streams. * <p> * Input: Nothing. diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 140336e93357..dce5d56e24a4 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -18,6 +18,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -289,6 +290,7 @@ public class VoicemailContract { * Path to the media content file. Internal only field. * @hide */ + @TestApi public static final String _DATA = "_data"; // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index aaba85bd36a7..8e0f522b5665 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -67,7 +67,7 @@ public abstract class AugmentedAutofillService extends Service { private static final String TAG = AugmentedAutofillService.class.getSimpleName(); - // TODO(b/111330312): STOPSHIP use dynamic value, or change to false + // TODO(b/123100811): STOPSHIP use dynamic value, or change to false static final boolean DEBUG = true; static final boolean VERBOSE = false; @@ -127,8 +127,6 @@ public abstract class AugmentedAutofillService extends Service { return false; } - // TODO(b/111330312): add methods to disable autofill per app / activity? - /** * Asks the service to handle an "augmented" autofill request. * @@ -175,12 +173,11 @@ public abstract class AugmentedAutofillService extends Service { focusedValue, requestTime, callback); mAutofillProxies.put(sessionId, proxy); } else { - // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging - // TODO(b/111330312): also make sure to cover scenario on CTS test + // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId); proxy.update(focusedId, focusedValue); } - // TODO(b/111330312): set cancellation signal + // TODO(b/123101711): set cancellation signal final CancellationSignal cancellationSignal = null; onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy), new FillCallback(proxy)); @@ -193,7 +190,7 @@ public abstract class AugmentedAutofillService extends Service { final int sessionId = mAutofillProxies.keyAt(i); final AutofillProxy proxy = mAutofillProxies.valueAt(i); if (proxy == null) { - // TODO(b/111330312): this might be fine, in which case we should logv it + // TODO(b/123100811): this might be fine, in which case we should logv it Log.w(TAG, "No proxy for session " + sessionId); return; } @@ -303,7 +300,7 @@ public abstract class AugmentedAutofillService extends Service { this.mFocusedId = focusedId; this.mFocusedValue = focusedValue; this.mRequestTime = requestTime; - // TODO(b/111330312): linkToDeath + // TODO(b/123099468): linkToDeath } @NonNull @@ -366,7 +363,7 @@ public abstract class AugmentedAutofillService extends Service { private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) { synchronized (mLock) { - // TODO(b/111330312): should we close the popupwindow if the focused id changed? + // TODO(b/123099468): should we close the popupwindow if the focused id changed? mFocusedId = focusedId; mFocusedValue = focusedValue; } @@ -425,7 +422,7 @@ public abstract class AugmentedAutofillService extends Service { default: Slog.w(TAG, "invalid event reported: " + event); } - // TODO(b/111330312): log metrics as well + // TODO(b/122858578): log metrics as well } public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index bfb4aadaeed9..f2a7a35b2825 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -59,7 +59,8 @@ public final class FillCallback { if (fillWindow != null) { fillWindow.show(); } - // TODO(b/111330312): properly implement on server-side by updating the Session state - // accordingly (and adding CTS tests) + // TODO(b/123099468): must notify the server so it can update the session state to avoid + // showing conflicting UIs (for example, if a new request is made to the main autofill + // service and it now wants to show something). } } diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index dad506763641..af9905f480df 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -29,7 +29,7 @@ import android.view.autofill.AutofillValue; * @hide */ @SystemApi -// TODO(b/111330312): pass a requestId and/or sessionId +// TODO(b/123100811): pass a requestId and/or sessionId? @TestApi // TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service // in the same package as the test, and that module is compiled with SDK=test_current diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java index 5285132b31a5..f1e904a7d8bc 100644 --- a/core/java/android/service/autofill/augmented/FillResponse.java +++ b/core/java/android/service/autofill/augmented/FillResponse.java @@ -19,8 +19,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.os.Parcel; -import android.os.Parcelable; import android.view.autofill.AutofillId; import java.util.List; @@ -34,7 +32,7 @@ import java.util.List; @TestApi //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service //in the same package as the test, and that module is compiled with SDK=test_current -public final class FillResponse implements Parcelable { +public final class FillResponse { private final FillWindow mFillWindow; @@ -70,8 +68,8 @@ public final class FillResponse implements Parcelable { * @return this builder */ public Builder setFillWindow(@NonNull FillWindow fillWindow) { - // TODO(b/111330312): implement / check not null / unit test - // TODO(b/111330312): throw exception if FillWindow not updated yet + // TODO(b/123100712): check not null / unit test / throw exception if FillWindow not + // updated yet mFillWindow = fillWindow; return this; } @@ -85,7 +83,7 @@ public final class FillResponse implements Parcelable { * @return this builder */ public Builder setIgnoredIds(@NonNull List<AutofillId> ids) { - // TODO(b/111330312): implement / check not null / unit test + // TODO(b/123100695): implement / check not null / unit test return this; } @@ -102,37 +100,10 @@ public final class FillResponse implements Parcelable { * @return A built response. */ public FillResponse build() { - // TODO(b/111330312): check conditions / add unit test + // TODO(b/123100712): check conditions / add unit test return new FillResponse(this); } - - // TODO(b/111330312): add methods to disable app / activity, either here or on manager - } - - // TODO(b/111330312): implement to String - - @Override - public int describeContents() { - return 0; } - @Override - public void writeToParcel(Parcel parcel, int flags) { - // TODO(b/111330312): implement - } - - public static final Parcelable.Creator<FillResponse> CREATOR = - new Parcelable.Creator<FillResponse>() { - - @Override - public FillResponse createFromParcel(Parcel parcel) { - // TODO(b/111330312): implement - return null; - } - - @Override - public FillResponse[] newArray(int size) { - return new FillResponse[size]; - } - }; + // TODO(b/123100811): implement to String } diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 51b0f01af6ae..40e3a1219501 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -20,7 +20,6 @@ import static android.service.autofill.augmented.AugmentedAutofillService.VERBOS import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -42,8 +41,6 @@ import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; /** * Handle to a window used to display the augmented autofill UI. @@ -71,18 +68,6 @@ import java.lang.annotation.RetentionPolicy; public final class FillWindow implements AutoCloseable { private static final String TAG = "FillWindow"; - /** Indicates the data being shown is a physical address */ - public static final long FLAG_METADATA_ADDRESS = 0x1; - - // TODO(b/111330312): add more flags - - /** @hide */ - @LongDef(prefix = { "FLAG" }, value = { - FLAG_METADATA_ADDRESS, - }) - @Retention(RetentionPolicy.SOURCE) - @interface Flags{} - private final Object mLock = new Object(); private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -108,29 +93,22 @@ public final class FillWindow implements AutoCloseable { * * @param rootView new root view * @param area coordinates to render the view. - * @param flags optional flags such as metadata of what will be rendered in the window. The - * Smart Suggestion host might decide whether or not to render the UI based on them. + * @param flags currently not used. * * @return boolean whether the window was updated or not. * * @throws IllegalArgumentException if the area is not compatible with this window */ - public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) { + public boolean update(@NonNull Area area, @NonNull View rootView, long flags) { if (DEBUG) { Log.d(TAG, "Updating " + area + " + with " + rootView); } - // TODO(b/111330312): add test case for null + // TODO(b/123100712): add test case for null Preconditions.checkNotNull(area); Preconditions.checkNotNull(rootView); - // TODO(b/111330312): must check the area is a valid object returned by + // TODO(b/123100712): must check the area is a valid object returned by // SmartSuggestionParams, throw IAE if not - // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider - - - // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating - // the window underneath the existing view. - final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams(); if (smartSuggestion == null) { Log.w(TAG, "No SmartSuggestionParams"); @@ -148,12 +126,12 @@ public final class FillWindow implements AutoCloseable { mProxy = area.proxy; - // TODO(b/111330312): once we have the SurfaceControl approach, we should update the + // TODO(b/123227534): once we have the SurfaceControl approach, we should update the // window instead of destroying. In fact, it might be better to allocate a full window // initially, which is transparent (and let touches get through) everywhere but in the // rect boundaries. - // TODO(b/111330312): make sure all touch events are handled, window is always closed, + // TODO(b/123099468): make sure all touch events are handled, window is always closed, // etc. mWm = rootView.getContext().getSystemService(WindowManager.class); @@ -181,7 +159,7 @@ public final class FillWindow implements AutoCloseable { /** @hide */ void show() { - // TODO(b/111330312): check if updated first / throw exception + // TODO(b/123100712): check if updated first / throw exception if (DEBUG) Log.d(TAG, "show()"); synchronized (mLock) { checkNotDestroyedLocked(); diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index dac75904f4fc..2b0726649759 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -24,8 +24,7 @@ import android.os.ICancellationSignal; * @hide */ interface IFillCallback { - // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it) + // TODO(b/123101711): add cancellation (after we have CTS tests, so we can test it) // void onCancellable(in ICancellationSignal cancellation); - // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions) void onSuccess(); } diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java index b60064e9dd69..1fb9032c9af5 100644 --- a/core/java/android/service/autofill/augmented/PresentationParams.java +++ b/core/java/android/service/autofill/augmented/PresentationParams.java @@ -190,7 +190,7 @@ public abstract class PresentationParams { */ @Nullable public Area getSubArea(@NonNull Rect bounds) { - // TODO(b/111330312): implement / check boundaries / throw IAE / add unit test + // TODO(b/123100712): implement / check boundaries / throw IAE / add unit test return null; } diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 302e1a656833..020de7f24048 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -116,6 +116,13 @@ public abstract class ContentCaptureService extends Service { mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession, ContentCaptureService.this, sessionId)); } + + @Override + public void onUserDataRemovalRequest(UserDataRemovalRequest request) { + mHandler.sendMessage( + obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest, + ContentCaptureService.this, request)); + } }; /** @@ -431,6 +438,10 @@ public abstract class ContentCaptureService extends Service { onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } + private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) { + onUserDataRemovalRequest(request); + } + /** * Checks if the given {@code uid} owns the session associated with the event. */ diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl index a8dd21392337..d92fb3bed679 100644 --- a/core/java/android/service/contentcapture/IContentCaptureService.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -19,6 +19,7 @@ package android.service.contentcapture; import android.os.IBinder; import android.service.contentcapture.SnapshotData; import android.view.contentcapture.ContentCaptureContext; +import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.os.IResultReceiver; @@ -36,4 +37,5 @@ oneway interface IContentCaptureService { in IResultReceiver clientReceiver); void onSessionFinished(String sessionId); void onActivitySnapshot(String sessionId, in SnapshotData snapshotData); + void onUserDataRemovalRequest(in UserDataRemovalRequest request); } diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index 4dc10cd2e4cc..ffb524d5c2e4 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -103,10 +103,23 @@ public abstract class EuiccService extends Service { */ public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; + /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */ public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + // LUI resolution actions. These are called by the platform to resolve errors in situations that // require user interaction. // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index de532b74375f..b6788f578bd6 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -15,8 +15,6 @@ */ package android.service.notification; -import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.Notification; import android.os.Bundle; import android.os.Parcel; @@ -24,10 +22,7 @@ import android.os.Parcelable; /** * Ranking updates from the Assistant. - * @hide */ -@SystemApi -@TestApi public final class Adjustment implements Parcelable { private final String mPackage; private final String mKey; @@ -39,6 +34,7 @@ public final class Adjustment implements Parcelable { * Data type: ArrayList of {@code String}, where each is a representation of a * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. * See {@link android.app.Notification.Builder#addPerson(String)}. + * @hide */ public static final String KEY_PEOPLE = "key_people"; /** @@ -46,6 +42,7 @@ public final class Adjustment implements Parcelable { * users. If a user chooses to snooze a notification until one of these criterion, the * assistant will be notified via * {@link NotificationAssistantService#onNotificationSnoozedUntilContext}. + * @hide */ public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; /** @@ -112,7 +109,7 @@ public final class Adjustment implements Parcelable { mUser = user; } - protected Adjustment(Parcel in) { + private Adjustment(Parcel in) { if (in.readInt() == 1) { mPackage = in.readString(); } else { diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index ad34ab3bddb1..e93b1580bc66 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -21,8 +21,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; -import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.admin.DevicePolicyManager; @@ -61,11 +59,7 @@ import java.util.List; * <p> * All callbacks are called on the main thread. * </p> - * - * @hide */ -@SystemApi -@TestApi public abstract class NotificationAssistantService extends NotificationListenerService { private static final String TAG = "NotificationAssistants"; @@ -109,6 +103,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS * * @param sbn the notification to snooze * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context. + * @hide */ abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn, String snoozeCriterionId); @@ -250,6 +245,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the * notification. * @param key The key of the notification to snooze + * @hide */ public final void unsnoozeNotification(String key) { if (!isBound()) return; diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 0e63cd37f3f2..c734b630759b 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1617,14 +1617,16 @@ public abstract class NotificationListenerService extends Service { } /** - * @hide + * Returns a list of smart {@link Notification.Action} that can be added by the + * {@link NotificationAssistantService} */ public List<Notification.Action> getSmartActions() { return mSmartActions; } /** - * @hide + * Returns a list of smart replies that can be added by the + * {@link NotificationAssistantService} */ public List<CharSequence> getSmartReplies() { return mSmartReplies; diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java index e5f3dfbe2a1c..814b4772a395 100644 --- a/core/java/android/service/notification/NotificationStats.java +++ b/core/java/android/service/notification/NotificationStats.java @@ -16,8 +16,6 @@ package android.service.notification; import android.annotation.IntDef; -import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.RemoteInput; import android.os.Parcel; import android.os.Parcelable; @@ -25,11 +23,6 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * @hide - */ -@TestApi -@SystemApi public final class NotificationStats implements Parcelable { private boolean mSeen; @@ -105,7 +98,7 @@ public final class NotificationStats implements Parcelable { public NotificationStats() { } - protected NotificationStats(Parcel in) { + private NotificationStats(Parcel in) { mSeen = in.readByte() != 0; mExpanded = in.readByte() != 0; mDirectReplied = in.readByte() != 0; diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index e105fdf6cfb8..2789651c4eaf 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -16,6 +16,7 @@ package android.service.voice; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -40,6 +41,8 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -77,6 +80,33 @@ public class VoiceInteractionService extends Service { */ public static final String SERVICE_META_DATA = "android.voice_interaction"; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"VOICE_STATE_"}, value = { + VOICE_STATE_NONE, + VOICE_STATE_CONDITIONAL_LISTENING, + VOICE_STATE_LISTENING, + VOICE_STATE_FULFILLING}) + public @interface VoiceState { + } + + /** + * Voice assistant inactive. + */ + public static final int VOICE_STATE_NONE = 0; + /** + * Voice assistant listening, but will only trigger if it hears a request it can fulfill. + */ + public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1; + /** + * Voice assistant is listening to user speech. + */ + public static final int VOICE_STATE_LISTENING = 2; + /** + * Voice assistant is fulfilling an action requested by the user. + */ + public static final int VOICE_STATE_FULFILLING = 3; + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { @@ -341,6 +371,43 @@ public class VoiceInteractionService extends Service { } } + /** + * Requests that the voice state UI indicate the given state. + * + * @param state value indicating whether the assistant is listening, fulfilling, etc. + */ + public final void setVoiceState(@VoiceState int state) { + try { + mSystemService.setVoiceState(state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Displays the given voice transcription contents. + */ + public final void setTranscription(@NonNull String transcription) { + try { + mSystemService.setTranscription(transcription); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Hides transcription. + * + * @param immediate if {@code true}, remove before transcription animation completes. + */ + public final void clearTranscription(boolean immediate) { + try { + mSystemService.clearTranscription(immediate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("VOICE INTERACTION"); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 0edcb3d8eb6a..4052ed782671 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -40,6 +40,7 @@ public class FeatureFlagUtils { public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled"; + public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled"; private static final Map<String, String> DEFAULT_FLAGS; private static final Set<String> OBSERVABLE_FLAGS; @@ -53,13 +54,14 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); DEFAULT_FLAGS.put("settings_slice_injection", "false"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); - DEFAULT_FLAGS.put("settings_wifi_dpp", "false"); - DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false"); - DEFAULT_FLAGS.put("settings_wifi_sharing", "false"); + DEFAULT_FLAGS.put("settings_wifi_dpp", "true"); + DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true"); + DEFAULT_FLAGS.put("settings_wifi_sharing", "true"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put(SAFETY_HUB, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false"); + DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false"); OBSERVABLE_FLAGS = new HashSet<>(); OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED); diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java new file mode 100644 index 000000000000..d5f048434b32 --- /dev/null +++ b/core/java/android/util/LongArrayQueue.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.GrowingArrayUtils; + +import libcore.util.EmptyArray; + +import java.util.NoSuchElementException; + +/** + * A lightweight implementation for a queue with long values. + * Additionally supports getting an element with a specified position from the head of the queue. + * The queue grows in size if needed to accommodate new elements. + * + * @hide + */ +public class LongArrayQueue { + + private long[] mValues; + private int mSize; + private int mHead; + private int mTail; + + /** + * Initializes a queue with the given starting capacity. + * + * @param initialCapacity the capacity. + */ + public LongArrayQueue(int initialCapacity) { + if (initialCapacity == 0) { + mValues = EmptyArray.LONG; + } else { + mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity); + } + mSize = 0; + mHead = mTail = 0; + } + + /** + * Initializes a queue with default starting capacity. + */ + public LongArrayQueue() { + this(16); + } + + private void grow() { + if (mSize < mValues.length) { + throw new IllegalStateException("Queue not full yet!"); + } + final int newSize = GrowingArrayUtils.growSize(mSize); + final long[] newArray = ArrayUtils.newUnpaddedLongArray(newSize); + final int r = mValues.length - mHead; // Number of elements on and to the right of head. + System.arraycopy(mValues, mHead, newArray, 0, r); + System.arraycopy(mValues, 0, newArray, r, mHead); + mValues = newArray; + mHead = 0; + mTail = mSize; + } + + /** + * Returns the number of elements in the queue. + */ + public int size() { + return mSize; + } + + /** + * Removes all elements from this queue. + */ + public void clear() { + mSize = 0; + mHead = mTail = 0; + } + + /** + * Adds a value to the tail of the queue. + * + * @param value the value to be added. + */ + public void addLast(long value) { + if (mSize == mValues.length) { + grow(); + } + mValues[mTail] = value; + mTail = (mTail + 1) % mValues.length; + mSize++; + } + + /** + * Removes an element from the head of the queue. + * + * @return the element at the head of the queue. + * @throws NoSuchElementException if the queue is empty. + */ + public long removeFirst() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + final long ret = mValues[mHead]; + mHead = (mHead + 1) % mValues.length; + mSize--; + return ret; + } + + /** + * Returns the element at the given position from the head of the queue, where 0 represents the + * head of the queue. + * + * @param position the position from the head of the queue. + * @return the element found at the given position. + * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or + * {@code position} >= {@link #size()} + */ + public long get(int position) { + if (position < 0 || position >= mSize) { + throw new IndexOutOfBoundsException("Index " + position + + " not valid for a queue of size " + mSize); + } + final int index = (mHead + position) % mValues.length; + return mValues[index]; + } + + /** + * Returns the element at the head of the queue, without removing it. + * + * @return the element at the head of the queue. + * @throws NoSuchElementException if the queue is empty + */ + public long peekFirst() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + return mValues[mHead]; + } + + /** + * Returns the element at the tail of the queue. + * + * @return the element at the tail of the queue. + * @throws NoSuchElementException if the queue is empty. + */ + public long peekLast() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1; + return mValues[index]; + } +} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index f58efc900427..e3a6bd7a6949 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -26,6 +26,7 @@ import android.app.KeyguardManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.ColorSpace; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -953,6 +954,24 @@ public final class Display { } /** + * Returns the preferred wide color space of the Display. + * The returned wide gamut color space is based on hardware capability and + * is preferred by the composition pipeline. + * Returns null if the display doesn't support wide color gamut. + * {@link Display#isWideColorGamut()}. + */ + @Nullable + public ColorSpace getPreferredWideGamutColorSpace() { + synchronized (this) { + updateDisplayInfoLocked(); + if (mDisplayInfo.isWideColorGamut()) { + return mGlobal.getPreferredWideGamutColorSpace(); + } + return null; + } + } + + /** * Gets the supported color modes of this device. * @hide */ diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 33b3ff4fef59..7d9ec70a4599 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -237,6 +237,16 @@ public class GestureDetector { private static final int LONG_PRESS = 2; private static final int TAP = 3; + /** + * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as + * scrolling, will be inhibited. However, to account for the possibility of incorrect + * classification, the default scrolling will only be inhibited if the gesture moves beyond + * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press + * timeout will be increased for some situations where the default behaviour + * is to cancel it. + */ + private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2; + private final Handler mHandler; @UnsupportedAppUsage private final OnGestureListener mListener; @@ -292,27 +302,27 @@ public class GestureDetector { @Override public void handleMessage(Message msg) { switch (msg.what) { - case SHOW_PRESS: - mListener.onShowPress(mCurrentDownEvent); - break; - - case LONG_PRESS: - dispatchLongPress(); - break; - - case TAP: - // If the user's finger is still down, do not count it as a tap - if (mDoubleTapListener != null) { - if (!mStillDown) { - mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); - } else { - mDeferConfirmSingleTap = true; + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null) { + if (!mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } else { + mDeferConfirmSingleTap = true; + } } - } - break; + break; - default: - throw new RuntimeException("Unknown message " + msg); //never + default: + throw new RuntimeException("Unknown message " + msg); //never } } } @@ -427,7 +437,7 @@ public class GestureDetector { if (context == null) { //noinspection deprecation touchSlop = ViewConfiguration.getTouchSlop(); - doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this + doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); //noinspection deprecation mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); @@ -605,6 +615,10 @@ public class GestureDetector { if (mInLongPress || mInContextClick) { break; } + + final int motionClassification = ev.getClassification(); + final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS); + final float scrollX = mLastFocusX - focusX; final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { @@ -615,6 +629,31 @@ public class GestureDetector { final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + + final boolean ambiguousGesture = + motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + final boolean shouldInhibitDefaultAction = + hasPendingLongPress && ambiguousGesture; + if (shouldInhibitDefaultAction) { + // Inhibit default long press + if (distance > slopSquare) { + // The default action here is to remove long press. But if the touch + // slop below gets increased, and we never exceed the modified touch + // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing* + // will happen in response to user input. To prevent this, + // reschedule long press with a modified timeout. + mHandler.removeMessages(LONG_PRESS); + final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() + + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER); + } + // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll + // until the gesture is resolved. + // However, for safety, simply increase the touch slop in case the + // classification is erroneous. Since the value is squared, multiply twice. + slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER; + } + if (distance > slopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; @@ -633,6 +672,12 @@ public class GestureDetector { mLastFocusX = focusX; mLastFocusY = focusY; } + final boolean deepPress = + motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + if (deepPress && hasPendingLongPress) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessage(LONG_PRESS); + } break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 7c1465b443e4..7291d0b2f10c 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -122,7 +122,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mController.scheduleApplyChangeInsets(); } - void applyChangeInsets(InsetsState state) { + @VisibleForTesting + public void applyChangeInsets(InsetsState state) { final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); ArrayList<SurfaceParams> params = new ArrayList<>(); if (offset.left != 0) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 4b1d1ec15750..2142c36f8803 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,17 +16,22 @@ package android.view; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import android.util.Property; import android.util.SparseArray; +import android.view.InsetsState.InternalInsetType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetType; -import android.view.InsetsState.InternalInsetType; import com.android.internal.annotations.VisibleForTesting; @@ -39,6 +44,41 @@ import java.util.ArrayList; */ public class InsetsController implements WindowInsetsController { + // TODO: Use animation scaling and more optimal duration. + private static final int ANIMATION_DURATION_MS = 400; + private static final int DIRECTION_NONE = 0; + private static final int DIRECTION_SHOW = 1; + private static final int DIRECTION_HIDE = 2; + @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) + private @interface AnimationDirection{} + + /** + * Translation animation evaluator. + */ + private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( + 0, + (int) (startValue.top + fraction * (endValue.top - startValue.top)), + 0, + (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + + /** + * Linear animation property + */ + private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> { + InsetsProperty() { + super(Insets.class, "Insets"); + } + + @Override + public Insets get(WindowInsetsAnimationController object) { + return object.getCurrentInsets(); + } + @Override + public void set(WindowInsetsAnimationController object, Insets value) { + object.changeInsets(value); + } + } + private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); @@ -58,6 +98,8 @@ public class InsetsController implements WindowInsetsController { private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); + private ObjectAnimator mAnimator; + private @AnimationDirection int mAnimationDirection; public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -122,7 +164,10 @@ public class InsetsController implements WindowInsetsController { public void onControlsChanged(InsetsSourceControl[] activeControls) { if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { - mTmpControlArray.put(activeControl.getType(), activeControl); + if (activeControl != null) { + // TODO(b/122982984): Figure out why it can be null. + mTmpControlArray.put(activeControl.getType(), activeControl); + } } } @@ -146,18 +191,40 @@ public class InsetsController implements WindowInsetsController { @Override public void show(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).show(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_HIDE) { + // Only one animator (with multiple InsetType) can run at a time. + // previous one should be cancelled for simplicity. + cancelExistingAnimation(); + } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) { + // no-op: already shown or animating in. + // TODO: When we have more than one types: handle specific case when + // show animation is going on, but the current type is not becoming visible. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, true /* show */); } @Override public void hide(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).hide(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_SHOW) { + cancelExistingAnimation(); + } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) { + // no-op: already hidden or animating out. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, false /* show */); } @Override @@ -226,20 +293,96 @@ public class InsetsController implements WindowInsetsController { } } + private void applyAnimation(@InsetType final int types, boolean show) { + if (types == 0) { + // nothing to animate. + return; + } + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + mAnimator = ObjectAnimator.ofObject( + controller, + new InsetsProperty(), + sEvaluator, + show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(), + show ? controller.getShownStateInsets() : controller.getHiddenStateInsets() + ); + mAnimator.setDuration(ANIMATION_DURATION_MS); + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + onAnimationFinish(); + } + + @Override + public void onAnimationEnd(Animator animation) { + onAnimationFinish(); + } + }); + mAnimator.start(); + } + + @Override + public void onCancelled() {} + + private void onAnimationFinish() { + mAnimationDirection = DIRECTION_NONE; + if (show) { + showOnAnimationEnd(types); + } else { + hideOnAnimationEnd(types); + } + } + }; + // TODO: Instead of clearing this here, properly wire up + // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. + mAnimationControls.clear(); + controlWindowInsetsAnimation(types, listener); + } + + private void hideOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).hide(); + } + } + + private void showOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).show(); + } + } + + /** + * Cancel on-going animation to show/hide {@link InsetType}. + */ + @VisibleForTesting + public void cancelExistingAnimation() { + mAnimationDirection = DIRECTION_NONE; + if (mAnimator != null) { + mAnimator.cancel(); + } + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); } - void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) { + @VisibleForTesting + public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) { mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation); } - void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) { + @VisibleForTesting + public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) { mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); } - void scheduleApplyChangeInsets() { + @VisibleForTesting + public void scheduleApplyChangeInsets() { if (!mAnimCallbackScheduled) { mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, mAnimCallback, null /* token*/); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 145b09763676..7937cb69b80e 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,8 +17,8 @@ package android.view; import android.annotation.Nullable; -import android.view.SurfaceControl.Transaction; import android.view.InsetsState.InternalInsetType; +import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; @@ -89,6 +89,11 @@ public class InsetsSourceConsumer { return true; } + @VisibleForTesting + public boolean isVisible() { + return mVisible; + } + private void setVisible(boolean visible) { if (mVisible == visible) { return; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 529776e542ee..a6af1a296faf 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,7 +16,10 @@ package android.view; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.indexOf; import android.annotation.IntDef; @@ -124,9 +127,10 @@ public class InsetsState implements Parcelable { @Nullable @InsetSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; + boolean[] typeVisibilityMap = new boolean[SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); - if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL && legacyContentInsets != null && legacyStableInsets != null) { WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); @@ -136,22 +140,29 @@ public class InsetsState implements Parcelable { if (source == null) { continue; } + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL + && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) { + typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); + continue; + } + processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, - typeSideMap); + typeSideMap, typeVisibilityMap); // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. if (source.getType() != TYPE_IME) { processSource(source, relativeFrameMax, true /* ignoreVisibility */, - typeMaxInsetsMap, null /* typeSideMap */); + typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */); } } - return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound, + return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, alwaysConsumeNavBar, cutout); } private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, - Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) { + Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap, + @Nullable boolean[] typeVisibilityMap) { Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); int index = indexOf(toPublicType(source.getType())); @@ -162,6 +173,10 @@ public class InsetsState implements Parcelable { typeInsetsMap[index] = Insets.max(existing, insets); } + if (typeVisibilityMap != null) { + typeVisibilityMap[index] = source.isVisible(); + } + if (typeSideMap != null && !Insets.NONE.equals(insets)) { @InsetSide int insetSide = getInsetSide(insets); if (insetSide != INSET_SIDE_UNKNWON) { diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java index 3c9ce788b706..6f5a85d210af 100644 --- a/core/java/android/view/RemoteAnimationAdapter.java +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -18,7 +18,6 @@ package android.view; import android.annotation.UnsupportedAppUsage; import android.app.ActivityOptions; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -52,6 +51,7 @@ public class RemoteAnimationAdapter implements Parcelable { private final IRemoteAnimationRunner mRunner; private final long mDuration; private final long mStatusBarTransitionDelay; + private final boolean mChangeNeedsSnapshot; /** @see #getCallingPid */ private int mCallingPid; @@ -59,21 +59,31 @@ public class RemoteAnimationAdapter implements Parcelable { /** * @param runner The interface that gets notified when we actually need to start the animation. * @param duration The duration of the animation. + * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by + * screenshotting the task. * @param statusBarTransitionDelay The desired delay for all visual animations in the * status bar caused by this app animation in millis. */ @UnsupportedAppUsage public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, - long statusBarTransitionDelay) { + long statusBarTransitionDelay, boolean changeNeedsSnapshot) { mRunner = runner; mDuration = duration; + mChangeNeedsSnapshot = changeNeedsSnapshot; mStatusBarTransitionDelay = statusBarTransitionDelay; } + @UnsupportedAppUsage + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay) { + this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */); + } + public RemoteAnimationAdapter(Parcel in) { mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); mDuration = in.readLong(); mStatusBarTransitionDelay = in.readLong(); + mChangeNeedsSnapshot = in.readBoolean(); } public IRemoteAnimationRunner getRunner() { @@ -88,6 +98,10 @@ public class RemoteAnimationAdapter implements Parcelable { return mStatusBarTransitionDelay; } + public boolean getChangeNeedsSnapshot() { + return mChangeNeedsSnapshot; + } + /** * To be called by system_server to keep track which pid is running this animation. */ @@ -112,6 +126,7 @@ public class RemoteAnimationAdapter implements Parcelable { dest.writeStrongInterface(mRunner); dest.writeLong(mDuration); dest.writeLong(mStatusBarTransitionDelay); + dest.writeBoolean(mChangeNeedsSnapshot); } public static final Creator<RemoteAnimationAdapter> CREATOR diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 567b279ead11..1d2cf4b78756 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -24,6 +24,8 @@ import static android.view.RemoteAnimationTargetProto.MODE; import static android.view.RemoteAnimationTargetProto.POSITION; import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX; import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS; +import static android.view.RemoteAnimationTargetProto.START_BOUNDS; +import static android.view.RemoteAnimationTargetProto.START_LEASH; import static android.view.RemoteAnimationTargetProto.TASK_ID; import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION; @@ -57,9 +59,15 @@ public class RemoteAnimationTarget implements Parcelable { */ public static final int MODE_CLOSING = 1; + /** + * The app is in the set of resizing apps (eg. mode change) of this transition. + */ + public static final int MODE_CHANGING = 2; + @IntDef(prefix = { "MODE_" }, value = { MODE_OPENING, - MODE_CLOSING + MODE_CLOSING, + MODE_CHANGING }) @Retention(RetentionPolicy.SOURCE) public @interface Mode {} @@ -83,6 +91,13 @@ public class RemoteAnimationTarget implements Parcelable { public final SurfaceControl leash; /** + * The {@link SurfaceControl} for the starting state of a target if this transition is + * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window. + */ + @UnsupportedAppUsage + public final SurfaceControl startLeash; + + /** * Whether the app is translucent and may reveal apps behind. */ @UnsupportedAppUsage @@ -128,6 +143,15 @@ public class RemoteAnimationTarget implements Parcelable { public final Rect sourceContainerBounds; /** + * The starting bounds of the source container in screen space coordinates. This is {@code null} + * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size + * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds + * is the end bounds of a change transition. + */ + @UnsupportedAppUsage + public final Rect startBounds; + + /** * The window configuration for the target. */ @UnsupportedAppUsage @@ -141,7 +165,8 @@ public class RemoteAnimationTarget implements Parcelable { public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, - Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents) { + Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents, + SurfaceControl startLeash, Rect startBounds) { this.mode = mode; this.taskId = taskId; this.leash = leash; @@ -153,6 +178,8 @@ public class RemoteAnimationTarget implements Parcelable { this.sourceContainerBounds = new Rect(sourceContainerBounds); this.windowConfiguration = windowConfig; this.isNotInRecents = isNotInRecents; + this.startLeash = startLeash; + this.startBounds = startBounds == null ? null : new Rect(startBounds); } public RemoteAnimationTarget(Parcel in) { @@ -167,6 +194,8 @@ public class RemoteAnimationTarget implements Parcelable { sourceContainerBounds = in.readParcelable(null); windowConfiguration = in.readParcelable(null); isNotInRecents = in.readBoolean(); + startLeash = in.readParcelable(null); + startBounds = in.readParcelable(null); } @Override @@ -187,6 +216,8 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeParcelable(sourceContainerBounds, 0 /* flags */); dest.writeParcelable(windowConfiguration, 0 /* flags */); dest.writeBoolean(isNotInRecents); + dest.writeParcelable(startLeash, 0 /* flags */); + dest.writeParcelable(startBounds, 0 /* flags */); } public void dump(PrintWriter pw, String prefix) { @@ -215,6 +246,8 @@ public class RemoteAnimationTarget implements Parcelable { position.writeToProto(proto, POSITION); sourceContainerBounds.writeToProto(proto, SOURCE_CONTAINER_BOUNDS); windowConfiguration.writeToProto(proto, WINDOW_CONFIGURATION); + startLeash.writeToProto(proto, START_LEASH); + startBounds.writeToProto(proto, START_BOUNDS); proto.end(token); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 863b717008d2..4032a6b84801 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -46,10 +46,9 @@ import android.hardware.display.DisplayedContentSamplingAttributes; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import android.view.Surface.OutOfResourcesException; @@ -60,6 +59,7 @@ import dalvik.system.CloseGuard; import libcore.util.NativeAllocationRegistry; import java.io.Closeable; +import java.nio.ByteBuffer; /** * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is @@ -75,7 +75,7 @@ public final class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, - int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid) + int w, int h, int format, int flags, long parentObject, Parcel metadata) throws OutOfResourcesException; private static native long nativeReadFromParcel(Parcel in); private static native long nativeCopyFromSurfaceControl(long nativeObject); @@ -182,6 +182,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken, IBinder toToken); private static native boolean nativeGetProtectedContentSupport(); + private static native void nativeSetMetadata(long transactionObj, int key, Parcel data); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -413,6 +414,24 @@ public final class SurfaceControl implements Parcelable { } /** + * owner UID. + * @hide + */ + public static final int METADATA_OWNER_UID = 1; + + /** + * Window type as per {@link WindowManager.LayoutParams}. + * @hide + */ + public static final int METADATA_WINDOW_TYPE = 2; + + /** + * Task id to allow association between surfaces and task. + * @hide + */ + public static final int METADATA_TASK_ID = 3; + + /** * Builder class for {@link SurfaceControl} objects. */ public static class Builder { @@ -423,8 +442,7 @@ public final class SurfaceControl implements Parcelable { private int mFormat = PixelFormat.OPAQUE; private String mName; private SurfaceControl mParent; - private int mWindowType = -1; - private int mOwnerUid = -1; + private SparseIntArray mMetadata; /** * Begin building a SurfaceControl with a given {@link SurfaceSession}. @@ -455,8 +473,8 @@ public final class SurfaceControl implements Parcelable { throw new IllegalArgumentException( "Only buffer layers can set a valid buffer size."); } - return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat, - mFlags, mParent, mWindowType, mOwnerUid); + return new SurfaceControl( + mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata); } /** @@ -581,23 +599,17 @@ public final class SurfaceControl implements Parcelable { } /** - * Set surface metadata. + * Sets a metadata int. * - * Currently these are window-types as per {@link WindowManager.LayoutParams} and - * owner UIDs. Child surfaces inherit their parents - * metadata so only the WindowManager needs to set this on root Surfaces. - * - * @param windowType A window-type - * @param ownerUid UID of the window owner. + * @param key metadata key + * @param data associated data * @hide */ - public Builder setMetadata(int windowType, int ownerUid) { - if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) { - throw new UnsupportedOperationException( - "It only makes sense to set Surface metadata from the WindowManager"); + public Builder setMetadata(int key, int data) { + if (mMetadata == null) { + mMetadata = new SparseIntArray(); } - mWindowType = windowType; - mOwnerUid = ownerUid; + mMetadata.put(key, data); return this; } @@ -682,13 +694,12 @@ public final class SurfaceControl implements Parcelable { * @param h The surface initial height. * @param flags The surface creation flags. Should always include {@link #HIDDEN} * in the creation flags. - * @param windowType The type of the window as specified in WindowManager.java. - * @param ownerUid A unique per-app ID. + * @param metadata Initial metadata. * * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, - SurfaceControl parent, int windowType, int ownerUid) + SurfaceControl parent, SparseIntArray metadata) throws OutOfResourcesException, IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name must not be null"); @@ -706,8 +717,21 @@ public final class SurfaceControl implements Parcelable { mName = name; mWidth = w; mHeight = h; - mNativeObject = nativeCreate(session, name, w, h, format, flags, - parent != null ? parent.mNativeObject : 0, windowType, ownerUid); + Parcel metaParcel = Parcel.obtain(); + try { + if (metadata != null && metadata.size() > 0) { + metaParcel.writeInt(metadata.size()); + for (int i = 0; i < metadata.size(); ++i) { + metaParcel.writeInt(metadata.keyAt(i)); + metaParcel.writeByteArray( + ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array()); + } + } + mNativeObject = nativeCreate(session, name, w, h, format, flags, + parent != null ? parent.mNativeObject : 0, metaParcel); + } finally { + metaParcel.recycle(); + } if (mNativeObject == 0) { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); @@ -2326,6 +2350,30 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets an arbitrary piece of metadata on the surface. This is a helper for int data. + * @hide + */ + public Transaction setMetadata(int key, int data) { + Parcel parcel = Parcel.obtain(); + parcel.writeInt(data); + try { + setMetadata(key, parcel); + } finally { + parcel.recycle(); + } + return this; + } + + /** + * Sets an arbitrary piece of metadata on the surface. + * @hide + */ + public Transaction setMetadata(int key, Parcel data) { + nativeSetMetadata(mNativeObject, key, data); + return this; + } + + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. * diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 34d076fba54d..47b206ca0dca 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.graphics.HardwareRenderer; +import android.graphics.Picture; import android.graphics.Point; import android.graphics.RecordingCanvas; import android.graphics.Rect; @@ -553,6 +554,10 @@ public final class ThreadedRenderer extends HardwareRenderer { dumpProfileInfo(fd, flags); } + Picture captureRenderingCommands() { + return null; + } + @Override public boolean loadSystemProperties() { boolean changed = super.loadSystemProperties(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2014ec2417ac..cd3decf4e981 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3985,6 +3985,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int SCROLL_AXIS_VERTICAL = 1 << 1; /** + * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain the default + * long press action will be inhibited. However, to account for the possibility of incorrect + * classification, the default long press timeout will instead be increased for some situations + * by the following factor. + * Likewise, the touch slop for allowing long press will be increased when gesture is uncertain. + */ + private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2; + + /** * Controls the over-scroll mode for this view. * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}, @@ -8203,10 +8212,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)} * respectively. The structure for the a child must be created using - * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the + * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the * {@code autofillId} for a child can be obtained either through * {@code childStructure.getAutofillId()} or - * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}. + * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}. * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> @@ -8608,7 +8617,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (isAttachedToWindow()) { throw new IllegalStateException("Cannot set autofill id when view is attached"); } - if (id != null && id.isVirtual()) { + if (id != null && !id.isNonVirtual()) { throw new IllegalStateException("Cannot set autofill id assigned to virtual views"); } if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) { @@ -9058,35 +9067,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid=" + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + ", visible=" + (getVisibility() == VISIBLE) - + ": alreadyNotifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)); + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - final ViewStructure structure = session.newViewStructure(this); - onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; + + // The code below doesn't take much for a unique view, but it's called for all views + // the first time the view hiearchy is laid off, which could acccumulative delay the + // initial layout. Hence, we're postponing it to a later stage - it might still cost a + // lost frame (or more), but that jank cost would only happen after the 1st layout. + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + final ViewStructure structure = session.newViewStructure(this); + onProvideContentCaptureStructure(structure, /* flags= */ 0); + session.notifyViewAppeared(structure); + }, /* token= */ null); } else { if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0 || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) { if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { - Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this - + ": notifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid=" + + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + + ", visible=" + (getVisibility() == VISIBLE) + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - session.notifyViewDisappeared(getAutofillId()); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, + () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null); } } @@ -9154,7 +9171,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ContentCaptureSession session = null; if (mParent instanceof View) { - session = ((View) mParent).getContentCaptureSession(); + session = ((View) mParent).getContentCaptureSession(ccm); } return session != null ? session : ccm.getMainContentCaptureSession(); @@ -13994,7 +14011,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (clickable) { setPressed(true, x, y); } - checkForLongClick(0, x, y); + checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); return true; } } @@ -14735,7 +14752,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mHasPerformedLongPress = false; if (!clickable) { - checkForLongClick(0, x, y); + checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); break; } @@ -14759,7 +14776,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); - checkForLongClick(0, x, y); + checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); } break; @@ -14780,8 +14797,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, drawableHotspotChanged(x, y); } + final int motionClassification = event.getClassification(); + final boolean ambiguousGesture = + motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + int touchSlop = mTouchSlop; + if (ambiguousGesture && hasPendingLongPressCallback()) { + if (!pointInView(x, y, touchSlop)) { + // The default action here is to cancel long press. But instead, we + // just extend the timeout here, in case the classification + // stays ambiguous. + removeLongPressCallback(); + long delay = ViewConfiguration.getLongPressTimeout() + * AMBIGUOUS_GESTURE_MULTIPLIER; + // Subtract the time already spent + delay -= event.getEventTime() - event.getDownTime(); + checkForLongClick(delay, x, y); + } + touchSlop *= AMBIGUOUS_GESTURE_MULTIPLIER; + } + // Be lenient about moving outside of buttons - if (!pointInView(x, y, mTouchSlop)) { + if (!pointInView(x, y, touchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); @@ -14791,6 +14827,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } + + final boolean deepPress = + motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + if (deepPress && hasPendingLongPressCallback()) { + // process the long click action immediately + removeLongPressCallback(); + checkForLongClick(0 /* send immediately */, x, y); + } + break; } @@ -14825,6 +14870,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Return true if the long press callback is scheduled to run sometime in the future. + * Return false if there is no scheduled long press callback at the moment. + */ + private boolean hasPendingLongPressCallback() { + if (mPendingCheckForLongPress == null) { + return false; + } + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo == null) { + return false; + } + return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress); + } + + /** * Remove the pending click action */ @UnsupportedAppUsage @@ -25434,7 +25494,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void checkForLongClick(int delayOffset, float x, float y) { + private void checkForLongClick(long delay, float x, float y) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; @@ -25444,8 +25504,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); - postDelayed(mPendingCheckForLongPress, - ViewConfiguration.getLongPressTimeout() - delayOffset); + postDelayed(mPendingCheckForLongPress, delay); } } @@ -27035,7 +27094,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); - checkForLongClick(ViewConfiguration.getTapTimeout(), x, y); + final long delay = + ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(); + checkForLongClick(delay, x, y); } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 292e933c3f7e..5afc07f35ba0 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -17,17 +17,21 @@ package android.view; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.HardwareRenderer; import android.graphics.Picture; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; import android.os.Debug; import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; import android.util.DisplayMetrics; import android.util.Log; @@ -48,16 +52,20 @@ import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; /** * Various debugging/tracing tools related to {@link View} and the view hierarchy. @@ -741,6 +749,123 @@ public class ViewDebug { root.getViewRootImpl().outputDisplayList(target); } + private static class PictureCallbackHandler implements AutoCloseable, + HardwareRenderer.PictureCapturedCallback, Runnable { + private final HardwareRenderer mRenderer; + private final Function<Picture, Boolean> mCallback; + private final Executor mExecutor; + private final ReentrantLock mLock = new ReentrantLock(false); + private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3); + private boolean mStopListening; + private Thread mRenderThread; + + private PictureCallbackHandler(HardwareRenderer renderer, + Function<Picture, Boolean> callback, Executor executor) { + mRenderer = renderer; + mCallback = callback; + mExecutor = executor; + mRenderer.setPictureCaptureCallback(this); + } + + @Override + public void close() { + mLock.lock(); + mStopListening = true; + mLock.unlock(); + mRenderer.setPictureCaptureCallback(null); + } + + @Override + public void onPictureCaptured(Picture picture) { + mLock.lock(); + if (mStopListening) { + mLock.unlock(); + mRenderer.setPictureCaptureCallback(null); + return; + } + if (mRenderThread == null) { + mRenderThread = Thread.currentThread(); + } + Picture toDestroy = null; + if (mQueue.size() == 3) { + toDestroy = mQueue.removeLast(); + } + mQueue.add(picture); + mLock.unlock(); + if (toDestroy == null) { + mExecutor.execute(this); + } else { + toDestroy.close(); + } + } + + @Override + public void run() { + mLock.lock(); + final Picture picture = mQueue.poll(); + final boolean isStopped = mStopListening; + mLock.unlock(); + if (Thread.currentThread() == mRenderThread) { + close(); + throw new IllegalStateException( + "ViewDebug#startRenderingCommandsCapture must be given an executor that " + + "invokes asynchronously"); + } + if (isStopped) { + picture.close(); + return; + } + final boolean keepReceiving = mCallback.apply(picture); + if (!keepReceiving) { + close(); + } + } + } + + /** + * Begins capturing the entire rendering commands for the view tree referenced by the given + * view. The view passed may be any View in the tree as long as it is attached. That is, + * {@link View#isAttachedToWindow()} must be true. + * + * Every time a frame is rendered a Picture will be passed to the given callback via the given + * executor. As long as the callback returns 'true' it will continue to receive new frames. + * The system will only invoke the callback at a rate that the callback is able to keep up with. + * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running + * then the callback will only receive 33% of the frames produced. + * + * This method must be called on the same thread as the View tree. + * + * @param tree The View tree to capture the rendering commands. + * @param callback The callback to invoke on every frame produced. Should return true to + * continue receiving new frames, false to stop capturing. + * @param executor The executor to invoke the callback on. Recommend using a background thread + * to avoid stalling the UI thread. Must be an asynchronous invoke or an + * exception will be thrown. + * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note + * that the callback may continue to receive another frame or two depending on thread timings. + * Returns null if the capture stream cannot be started, such as if there's no + * HardwareRenderer for the given view tree. + * @hide + */ + @TestApi + @Nullable + public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor, + Function<Picture, Boolean> callback) { + final View.AttachInfo attachInfo = tree.mAttachInfo; + if (attachInfo == null) { + throw new IllegalArgumentException("Given view isn't attached"); + } + if (attachInfo.mHandler.getLooper() != Looper.myLooper()) { + throw new IllegalStateException("Called on the wrong thread." + + " Must be called on the thread that owns the given View"); + } + final HardwareRenderer renderer = attachInfo.mThreadedRenderer; + if (renderer != null) { + return new PictureCallbackHandler(renderer, callback, executor); + } + return null; + } + private static void capture(View root, final OutputStream clientStream, String parameter) throws IOException { diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index e8088303eac7..c1536ae2b4ae 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -66,6 +66,7 @@ public final class WindowInsets { private final Insets[] mTypeInsetsMap; private final Insets[] mTypeMaxInsetsMap; + private final boolean[] mTypeVisibilityMap; @Nullable private Rect mTempRect; private final boolean mIsRound; @@ -106,6 +107,7 @@ public final class WindowInsets { public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), + createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), isRound, alwaysConsumeNavBar, displayCutout); } @@ -122,7 +124,9 @@ public final class WindowInsets { * @hide */ public WindowInsets(@Nullable Insets[] typeInsetsMap, - @Nullable Insets[] typeMaxInsetsMap, boolean isRound, + @Nullable Insets[] typeMaxInsetsMap, + boolean[] typeVisibilityMap, + boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed @@ -134,6 +138,7 @@ public final class WindowInsets { ? new Insets[SIZE] : typeMaxInsetsMap.clone(); + mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; mAlwaysConsumeNavBar = alwaysConsumeNavBar; @@ -148,8 +153,8 @@ public final class WindowInsets { * @param src Source to copy insets from */ public WindowInsets(WindowInsets src) { - this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar, - displayCutoutCopyConstructorArgument(src)); + this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, + src.mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(src)); } private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) { @@ -200,7 +205,7 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, false, false, null); + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null); } /** @@ -225,6 +230,20 @@ public final class WindowInsets { typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom); } + private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) { + boolean[] typeVisibilityMap = new boolean[SIZE]; + if (typeInsetMap == null) { + return typeVisibilityMap; + } + for (int i = FIRST; i <= LAST; i = i << 1) { + int index = indexOf(i); + if (!Insets.NONE.equals(typeInsetMap[index])) { + typeVisibilityMap[index] = true; + } + } + return typeVisibilityMap; + } + /** * Used to provide a safe copy of the system window insets to pass through * to the existing fitSystemWindows method and other similar internals. @@ -297,6 +316,27 @@ public final class WindowInsets { } /** + * Returns whether a set of windows that may cause insets is currently visible on screen, + * regardless of whether it actually overlaps with this window. + * + * @param typeMask Bit mask of {@link InsetType}s to query visibility status. + * @return {@code true} if and only if all windows included in {@code typeMask} are currently + * visible on screen. + * @hide pending unhide + */ + public boolean isVisible(@InsetType int typeMask) { + for (int i = FIRST; i <= LAST; i = i << 1) { + if ((typeMask & i) == 0) { + continue; + } + if (!mTypeVisibilityMap[indexOf(i)]) { + return false; + } + } + return true; + } + + /** * Returns the left system window inset in pixels. * * <p>The system window inset represents the area of a full-screen window that is @@ -392,6 +432,7 @@ public final class WindowInsets { public WindowInsets consumeDisplayCutout() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, null /* displayCutout */); } @@ -437,6 +478,7 @@ public final class WindowInsets { @NonNull public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(this)); } @@ -594,7 +636,7 @@ public final class WindowInsets { @NonNull public WindowInsets consumeStableInsets() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null, - mIsRound, mAlwaysConsumeNavBar, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(this)); } @@ -671,6 +713,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom), + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, mDisplayCutoutConsumed ? null @@ -692,14 +735,15 @@ public final class WindowInsets { && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap) && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap) + && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) && Objects.equals(mDisplayCutout, that.mDisplayCutout); } @Override public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), - mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed, - mStableInsetsConsumed, mDisplayCutoutConsumed); + Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, + mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed); } @@ -754,6 +798,7 @@ public final class WindowInsets { private final Insets[] mTypeInsetsMap; private final Insets[] mTypeMaxInsetsMap; + private final boolean[] mTypeVisibilityMap; private boolean mSystemInsetsConsumed = true; private boolean mStableInsetsConsumed = true; @@ -768,6 +813,7 @@ public final class WindowInsets { public Builder() { mTypeInsetsMap = new Insets[SIZE]; mTypeMaxInsetsMap = new Insets[SIZE]; + mTypeVisibilityMap = new boolean[SIZE]; } /** @@ -778,6 +824,7 @@ public final class WindowInsets { public Builder(WindowInsets insets) { mTypeInsetsMap = insets.mTypeInsetsMap.clone(); mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone(); + mTypeVisibilityMap = insets.mTypeVisibilityMap.clone(); mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed; mStableInsetsConsumed = insets.mStableInsetsConsumed; mDisplayCutout = displayCutoutCopyConstructorArgument(insets); @@ -862,6 +909,29 @@ public final class WindowInsets { } /** + * Sets whether windows that can cause insets are currently visible on screen. + * + * + * @see #isVisible(int) + * + * @param typeMask The bitmask of {@link InsetType} to set the visibility for. + * @param visible Whether to mark the windows as visible or not. + * + * @return itself + * @hide pending unhide + */ + @NonNull + public Builder setVisible(@InsetType int typeMask, boolean visible) { + for (int i = FIRST; i <= LAST; i = i << 1) { + if ((typeMask & i) == 0) { + continue; + } + mTypeVisibilityMap[indexOf(i)] = visible; + } + return this; + } + + /** * Sets the stable insets in pixels. * * <p>The stable inset represents the area of a full-screen window that <b>may</b> be @@ -916,8 +986,8 @@ public final class WindowInsets { @NonNull public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, - mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound, - mAlwaysConsumeNavBar, mDisplayCutout); + mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, + mIsRound, mAlwaysConsumeNavBar, mDisplayCutout); } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index a35be273f3bf..b70832315e2c 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.WindowInsets.Type.ime; + import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetType; @@ -32,11 +34,11 @@ public interface WindowInsetsController { * <p> * Note that if the window currently doesn't have control over a certain type, it will apply the * change as soon as the window gains control. The app can listen to the event by observing - * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in - * {@link WindowInsets}. + * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. * * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app * would like to make appear on screen. + * @hide */ void show(@InsetType int types); @@ -45,11 +47,11 @@ public interface WindowInsetsController { * <p> * Note that if the window currently doesn't have control over a certain type, it will apply the * change as soon as the window gains control. The app can listen to the event by observing - * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in - * {@link WindowInsets}. + * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. * * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app * would like to make disappear. + * @hide */ void hide(@InsetType int types); @@ -60,7 +62,50 @@ public interface WindowInsetsController { * @param types The {@link InsetType}s the application has requested to control. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. + * @hide */ void controlWindowInsetsAnimation(@InsetType int types, @NonNull WindowInsetsAnimationControlListener listener); + + /** + * Lets the application control the animation for showing the IME in a frame-by-frame manner by + * modifying the position of the IME when it's causing insets. + * + * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the + * IME are ready to be controlled, among other callbacks. + */ + default void controlInputMethodAnimation( + @NonNull WindowInsetsAnimationControlListener listener) { + controlWindowInsetsAnimation(ime(), listener); + } + + /** + * Makes the IME appear on screen. + * <p> + * Note that if the window currently doesn't have control over the IME, because it doesn't have + * focus, it will apply the change as soon as the window gains control. The app can listen to + * the event by observing {@link View#onApplyWindowInsets} and checking visibility with + * {@link WindowInsets#isVisible}. + * + * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener) + * @see #hideInputMethod() + */ + default void showInputMethod() { + show(ime()); + } + + /** + * Makes the IME disappear on screen. + * <p> + * Note that if the window currently doesn't have control over IME, because it doesn't have + * focus, it will apply the change as soon as the window gains control. The app can listen to + * the event by observing {@link View#onApplyWindowInsets} and checking visibility with + * {@link WindowInsets#isVisible}. + * + * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener) + * @see #showInputMethod() + */ + default void hideInputMethod() { + hide(ime()); + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 45c36516b885..6326c591aa04 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -255,6 +255,12 @@ public interface WindowManager extends ViewManager { int TRANSIT_CRASHING_ACTIVITY_CLOSE = 26; /** + * A task is changing windowing modes + * @hide + */ + int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27; + + /** * @hide */ @IntDef(prefix = { "TRANSIT_" }, value = { @@ -280,7 +286,8 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_TRANSLUCENT_ACTIVITY_OPEN, TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE, - TRANSIT_CRASHING_ACTIVITY_CLOSE + TRANSIT_CRASHING_ACTIVITY_CLOSE, + TRANSIT_TASK_CHANGE_WINDOWING_MODE }) @Retention(RetentionPolicy.SOURCE) @interface TransitionType {} diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index c5c1bcae232a..6aafa348e3f5 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1166,9 +1166,10 @@ public final class AccessibilityManager { /** * Notifies that the accessibility button in the system's navigation area has been clicked * + * @param displayId The logical display id. * @hide */ - public void notifyAccessibilityButtonClicked() { + public void notifyAccessibilityButtonClicked(int displayId) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1177,7 +1178,7 @@ public final class AccessibilityManager { } } try { - service.notifyAccessibilityButtonClicked(); + service.notifyAccessibilityButtonClicked(displayId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 38dac94340bb..486b35d9cc0f 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -63,7 +63,7 @@ interface IAccessibilityManager { IBinder getWindowToken(int windowId, int userId); - void notifyAccessibilityButtonClicked(); + void notifyAccessibilityButtonClicked(int displayId); void notifyAccessibilityButtonVisibilityChanged(boolean available); diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java index 9c935af09cca..f1c7b695ce05 100644 --- a/core/java/android/view/autofill/AutofillId.java +++ b/core/java/android/view/autofill/AutofillId.java @@ -29,12 +29,14 @@ public final class AutofillId implements Parcelable { /** @hide */ public static final int NO_SESSION = 0; - private static final int FLAG_IS_VIRTUAL = 0x1; - private static final int FLAG_HAS_SESSION = 0x2; + private static final int FLAG_IS_VIRTUAL_INT = 0x1; + private static final int FLAG_IS_VIRTUAL_LONG = 0x2; + private static final int FLAG_HAS_SESSION = 0x4; private final int mViewId; private final int mFlags; - private final int mVirtualId; + private final int mVirtualIntId; + private final long mVirtualLongId; private final int mSessionId; /** @hide */ @@ -46,40 +48,89 @@ public final class AutofillId implements Parcelable { /** @hide */ @TestApi public AutofillId(@NonNull AutofillId parent, int virtualChildId) { - this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION); + this(FLAG_IS_VIRTUAL_INT, parent.mViewId, virtualChildId, NO_SESSION); } /** @hide */ public AutofillId(int parentId, int virtualChildId) { - this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION); + this(FLAG_IS_VIRTUAL_INT, parentId, virtualChildId, NO_SESSION); } /** @hide */ - public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) { - this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId); + public AutofillId(@NonNull AutofillId parent, long virtualChildId, int sessionId) { + this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId); } - private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) { + private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) { mFlags = flags; mViewId = parentId; - mVirtualId = virtualChildId; + mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID; + mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID; mSessionId = sessionId; } - /** @hide */ public int getViewId() { return mViewId; } - /** @hide */ - public int getVirtualChildId() { - return mVirtualId; + /** + * Gets the virtual child id. + * + * <p>Should only be used on subsystems where such id is represented by an {@code int} + * (Assist and Autofill). + * + * @hide + */ + public int getVirtualChildIntId() { + return mVirtualIntId; } - /** @hide */ - public boolean isVirtual() { - return (mFlags & FLAG_IS_VIRTUAL) != 0; + /** + * Gets the virtual child id. + * + * <p>Should only be used on subsystems where such id is represented by a {@code long} + * (ContentCapture). + * + * @hide + */ + public long getVirtualChildLongId() { + return mVirtualLongId; + } + + /** + * Checks whether this node represents a virtual child, whose id is represented by an + * {@code int}. + * + * <p>Should only be used on subsystems where such id is represented by an {@code int} + * (Assist and Autofill). + * + * @hide + */ + public boolean isVirtualInt() { + return (mFlags & FLAG_IS_VIRTUAL_INT) != 0; + } + + /** + * Checks whether this node represents a virtual child, whose id is represented by an + * {@code long}. + * + * <p>Should only be used on subsystems where such id is represented by a {@code long} + * (ContentCapture). + * + * @hide + */ + public boolean isVirtualLong() { + return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0; + } + + /** + * Checks whether this node represents a non-virtual child. + * + * @hide + */ + public boolean isNonVirtual() { + return !isVirtualInt() && !isVirtualLong(); } private boolean hasSession() { @@ -100,7 +151,8 @@ public final class AutofillId implements Parcelable { final int prime = 31; int result = 1; result = prime * result + mViewId; - result = prime * result + mVirtualId; + result = prime * result + mVirtualIntId; + result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32)); result = prime * result + mSessionId; return result; } @@ -112,7 +164,8 @@ public final class AutofillId implements Parcelable { if (getClass() != obj.getClass()) return false; final AutofillId other = (AutofillId) obj; if (mViewId != other.mViewId) return false; - if (mVirtualId != other.mVirtualId) return false; + if (mVirtualIntId != other.mVirtualIntId) return false; + if (mVirtualLongId != other.mVirtualLongId) return false; if (mSessionId != other.mSessionId) return false; return true; } @@ -120,9 +173,12 @@ public final class AutofillId implements Parcelable { @Override public String toString() { final StringBuilder builder = new StringBuilder().append(mViewId); - if (isVirtual()) { - builder.append(':').append(mVirtualId); + if (isVirtualInt()) { + builder.append(':').append(mVirtualIntId); + } else if (isVirtualLong()) { + builder.append(':').append(mVirtualLongId); } + if (hasSession()) { builder.append('@').append(mSessionId); } @@ -138,12 +194,14 @@ public final class AutofillId implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mViewId); parcel.writeInt(mFlags); - if (isVirtual()) { - parcel.writeInt(mVirtualId); - } if (hasSession()) { parcel.writeInt(mSessionId); } + if (isVirtualInt()) { + parcel.writeInt(mVirtualIntId); + } else if (isVirtualLong()) { + parcel.writeLong(mVirtualLongId); + } } public static final Parcelable.Creator<AutofillId> CREATOR = @@ -152,9 +210,14 @@ public final class AutofillId implements Parcelable { public AutofillId createFromParcel(Parcel source) { final int viewId = source.readInt(); final int flags = source.readInt(); - final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID; final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION; - return new AutofillId(flags, viewId, virtualId, sessionId); + if ((flags & FLAG_IS_VIRTUAL_INT) != 0) { + return new AutofillId(flags, viewId, source.readInt(), sessionId); + } + if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) { + return new AutofillId(flags, viewId, source.readLong(), sessionId); + } + return new AutofillId(flags, viewId, View.NO_ID, sessionId); } @Override diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 888a4c57751e..64c34f612048 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -17,6 +17,7 @@ package android.view.autofill; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; +import static android.util.DebugUtils.flagsToString; import static android.view.autofill.Helper.sDebug; import static android.view.autofill.Helper.sVerbose; @@ -25,6 +26,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.ComponentName; @@ -77,6 +79,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; //TODO: use java.lang.ref.Cleaner once Android supports Java 9 import sun.misc.Cleaner; @@ -336,6 +339,25 @@ public final class AutofillManager { public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes /** + * Displays the Augment Autofill window using the same mechanism (such as a popup-window + * attached to the focused view) as the standard autofill. + * + * @hide + */ + @TestApi + public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1; + + /** @hide */ // TODO(b/123233342): remove when not used anymore + public static final int FLAG_SMART_SUGGESTION_LEGACY = 0x2; + + /** @hide */ + @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = { + FLAG_SMART_SUGGESTION_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SmartSuggestionMode {} + + /** * Makes an authentication id from a request id and a dataset id. * * @param requestId The request id. @@ -1686,7 +1708,7 @@ public final class AutofillManager { final IAutoFillManager service = mService; final IAutoFillManagerClient serviceClient = mServiceClient; mServiceClientCleaner = Cleaner.create(this, () -> { - // TODO(b/111330312): call service to also remove reference to + // TODO(b/123100811): call service to also remove reference to // mAugmentedAutofillServiceClient try { service.removeClient(serviceClient, userId); @@ -1746,6 +1768,108 @@ public final class AutofillManager { } } + /** + * Defines whether augmented autofill should be triggered for activities with such + * {@link android.content.ComponentName}. + * + * <p>Useful to blacklist a particular activity. + * + * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill + * service, and it's ignored if the caller isn't it. + * + * @hide + */ + @SystemApi + @TestApi + //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service + //in the same package as the test, and that module is compiled with SDK=test_current + public void setActivityAugmentedAutofillEnabled(@NonNull ComponentName activity, + boolean enabled) { + // TODO(b/123100824): implement + } + + /** + * Defines whether augmented autofill should be triggered for activities of the app with such + * {@code packageName}. + * + * <p>Useful to blacklist any activity from a particular app. + * + * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill + * service, and it's ignored if the caller isn't it. + * + * @hide + */ + @SystemApi + @TestApi + //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service + //in the same package as the test, and that module is compiled with SDK=test_current + public void setPackageAugmentedAutofillEnabled(@NonNull String packageName, boolean enabled) { + // TODO(b/123100824): implement + } + + /** + * Explicitly limits augmented autofill to the given packages and activities. + * + * <p>When the whitelist is set, it overrides the values passed to + * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)} + * and {@link #setPackageAugmentedAutofillEnabled(String, boolean)}. + * + * <p>To reset the whitelist, call it passing {@code null} to both arguments. + * + * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like + * apps that uses addresses. For example, if the service wants to support augmented autofill on + * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2} + * of {@code AddressApp2}, it would call: + * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"), + * Arrays.asList(new ComponentName("AddressApp2", "act1"), + * new ComponentName("AddressApp2", "act2")));} + * + * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill + * service, and it's ignored if the caller isn't it. + * + * @hide + */ + @SystemApi + @TestApi + //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service + //in the same package as the test, and that module is compiled with SDK=test_current + public void setAugmentedAutofillWhitelist(@Nullable List<String> packages, + @Nullable List<ComponentName> activities) { + // TODO(b/123100824): implement + } + + /** + * Gets the activities where augmented autofill was disabled by + * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}. + * + * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill + * service, and it's ignored if the caller isn't it. + * + * @hide + */ + @SystemApi + @TestApi + @NonNull + public Set<ComponentName> getAugmentedAutofillDisabledActivities() { + return null; // TODO(b/123100824): implement + } + + /** + * Gets the apps where content capture was disabled by + * {@link #setPackageAugmentedAutofillEnabled(String, boolean)}. + * + * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill + * service, and it's ignored if the caller isn't it. + * + * @hide + */ + @SystemApi + @TestApi + @NonNull + public Set<String> getAugmentedAutofillDisabledPackages() { + return null; // TODO(b/123100824): implement + } + private void requestShowFillUi(int sessionId, AutofillId id, int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) { final View anchor = findView(id); @@ -1769,8 +1893,8 @@ public final class AutofillManager { } if (callback != null) { - if (id.isVirtual()) { - callback.onAutofillEvent(anchor, id.getVirtualChildId(), + if (id.isVirtualInt()) { + callback.onAutofillEvent(anchor, id.getVirtualChildIntId(), AutofillCallback.EVENT_INPUT_SHOWN); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN); @@ -1896,7 +2020,7 @@ public final class AutofillManager { failedIds.add(id); continue; } - if (id.isVirtual()) { + if (id.isVirtualInt()) { if (virtualValues == null) { // Most likely there will be just one view with virtual children. virtualValues = new ArrayMap<>(1); @@ -1907,7 +2031,7 @@ public final class AutofillManager { valuesByParent = new SparseArray<>(5); virtualValues.put(view, valuesByParent); } - valuesByParent.put(id.getVirtualChildId(), value); + valuesByParent.put(id.getVirtualChildIntId(), value); } else { // Mark the view as to be autofilled with 'value' if (mLastAutofilledData == null) { @@ -2142,8 +2266,8 @@ public final class AutofillManager { } if (callback != null) { - if (id.isVirtual()) { - callback.onAutofillEvent(anchor, id.getVirtualChildId(), + if (id.isVirtualInt()) { + callback.onAutofillEvent(anchor, id.getVirtualChildIntId(), AutofillCallback.EVENT_INPUT_HIDDEN); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN); @@ -2169,8 +2293,8 @@ public final class AutofillManager { } if (callback != null) { - if (id.isVirtual()) { - callback.onAutofillEvent(anchor, id.getVirtualChildId(), + if (id.isVirtualInt()) { + callback.onAutofillEvent(anchor, id.getVirtualChildIntId(), AutofillCallback.EVENT_INPUT_UNAVAILABLE); } else { callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE); @@ -2296,6 +2420,11 @@ public final class AutofillManager { } } + /** @hide */ + public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) { + return flagsToString(AutofillManager.class, "FLAG_SMART_SUGGESTION_", flags); + } + @GuardedBy("mLock") private boolean isActiveLocked() { return mState == STATE_ACTIVE; @@ -2972,7 +3101,6 @@ public final class AutofillManager { @Override public Rect getViewCoordinates(@NonNull AutofillId id) { - // TODO(b/111330312): use handler / callback? final AutofillManager afm = mAfm.get(); if (afm == null) return null; diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java index 2d2987a035da..19286135532b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureContext.java +++ b/core/java/android/view/contentcapture/ContentCaptureContext.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -84,9 +85,9 @@ public final class ContentCaptureContext implements Parcelable { // Fields below are set by app on Builder private final @Nullable Bundle mExtras; private final @Nullable Uri mUri; + private final @Nullable String mAction; // Fields below are set by server when the session starts - // TODO(b/111276913): create new object for taskId + componentName / reuse on other places private final @Nullable ComponentName mComponentName; private final int mTaskId; private final int mDisplayId; @@ -102,10 +103,12 @@ public final class ContentCaptureContext implements Parcelable { mHasClientContext = true; mExtras = clientContext.mExtras; mUri = clientContext.mUri; + mAction = clientContext.mAction; } else { mHasClientContext = false; mExtras = null; mUri = null; + mAction = null; } mComponentName = Preconditions.checkNotNull(componentName); mTaskId = taskId; @@ -117,13 +120,14 @@ public final class ContentCaptureContext implements Parcelable { mHasClientContext = true; mExtras = builder.mExtras; mUri = builder.mUri; + mAction = builder.mAction; mComponentName = null; mTaskId = mFlags = mDisplayId = 0; } /** - * Gets the (optional) extras set by the app. + * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}). * * <p>It can be used to provide vendor-specific data that can be modified and examined. * @@ -136,7 +140,7 @@ public final class ContentCaptureContext implements Parcelable { } /** - * Gets the (optional) URI set by the app. + * Gets the (optional) URI set by the app (through {@link Builder#setUri(Uri)}). * * @hide */ @@ -147,6 +151,17 @@ public final class ContentCaptureContext implements Parcelable { } /** + * Gets the (optional) action set by the app (through {@link Builder#setAction(String)}). + * + * @hide + */ + @SystemApi + @Nullable + public String getAction() { + return mAction; + } + + /** * Gets the id of the {@link TaskInfo task} associated with this context. * * @hide @@ -213,6 +228,8 @@ public final class ContentCaptureContext implements Parcelable { public static final class Builder { private Bundle mExtras; private Uri mUri; + private boolean mDestroyed; + private String mAction; /** * Sets extra options associated with this context. @@ -221,11 +238,13 @@ public final class ContentCaptureContext implements Parcelable { * * @param extras extra options. * @return this builder. + * + * @throws IllegalStateException if {@link #build()} was already called. */ @NonNull public Builder setExtras(@NonNull Bundle extras) { - // TODO(b/111276913): check build just once / throw exception / test / document mExtras = Preconditions.checkNotNull(extras); + throwIfDestroyed(); return this; } @@ -236,23 +255,51 @@ public final class ContentCaptureContext implements Parcelable { * * @param uri URI associated with this context. * @return this builder. + * + * @throws IllegalStateException if {@link #build()} was already called. */ @NonNull public Builder setUri(@NonNull Uri uri) { - // TODO(b/111276913): check build just once / throw exception / test / document mUri = Preconditions.checkNotNull(uri); + throwIfDestroyed(); + return this; + } + + /** + * Sets an {@link Intent#getAction() intent action} associated with this context. + * + * @param action intent action + * + * @return this builder + * + * @throws IllegalStateException if {@link #build()} was already called. + */ + @NonNull + public Builder setAction(@NonNull String action) { + mAction = Preconditions.checkNotNull(action); + throwIfDestroyed(); return this; } /** * Builds the {@link ContentCaptureContext}. + * + * @throws IllegalStateException if {@link #build()} was already called or no call to either + * {@link #setExtras(Bundle)}, {@link #setAction(String)}, or {@link #setUri(Uri)} was made. + * + * @return the built {@code ContentCaptureContext} */ public ContentCaptureContext build() { - // TODO(b/111276913): check build just once / throw exception / test / document - // TODO(b/111276913): make sure it at least one property (uri / extras) / test / - // throw exception / documment + throwIfDestroyed(); + Preconditions.checkState(mExtras != null || mUri != null || mAction != null, + "Must call setUri() or setExtras() or setUri() before calling build()"); + mDestroyed = true; return new ContentCaptureContext(this); } + + private void throwIfDestroyed() { + Preconditions.checkState(!mDestroyed, "Already called #build()"); + } } /** @@ -277,6 +324,10 @@ public final class ContentCaptureContext implements Parcelable { // NOTE: cannot dump because it could contain PII pw.print(", hasUri"); } + if (mAction != null) { + // NOTE: cannot dump because it could contain PII + pw.print(", hasAction"); + } } @Override @@ -297,6 +348,10 @@ public final class ContentCaptureContext implements Parcelable { // NOTE: cannot print because it could contain PII builder.append(", hasUri"); } + if (mAction != null) { + // NOTE: cannot print because it could contain PII + builder.append(", hasAction"); + } return builder.append(']').toString(); } @@ -310,6 +365,7 @@ public final class ContentCaptureContext implements Parcelable { parcel.writeInt(mHasClientContext ? 1 : 0); if (mHasClientContext) { parcel.writeParcelable(mUri, flags); + parcel.writeString(mAction); parcel.writeBundle(mExtras); } parcel.writeParcelable(mComponentName, flags); @@ -329,12 +385,14 @@ public final class ContentCaptureContext implements Parcelable { final ContentCaptureContext clientContext; if (hasClientContext) { + // Must reconstruct the client context using the Builder API final Builder builder = new Builder(); final Uri uri = parcel.readParcelable(null); + final String action = parcel.readString(); final Bundle extras = parcel.readBundle(); if (uri != null) builder.setUri(uri); + if (action != null) builder.setAction(action); if (extras != null) builder.setExtras(extras); - // Must reconstruct the client context using the Builder API clientContext = new ContentCaptureContext(builder); } else { clientContext = null; diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 43963c3054e5..a6d44729aee5 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -15,6 +15,8 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -267,8 +269,7 @@ public final class ContentCaptureEvent implements Parcelable { pw.print(", parentSessionId="); pw.print(mParentSessionId); } if (mText != null) { - // Cannot print content because could have PII - pw.print(", text="); pw.print(mText.length()); pw.print("_chars"); + pw.print(", text="); pw.println(getSanitizedString(mText)); } } @@ -293,6 +294,9 @@ public final class ContentCaptureEvent implements Parcelable { } string.append(", id=").append(mNode.getAutofillId()); } + if (mText != null) { + string.append(", text=").append(getSanitizedString(mText)); + } return string.append(']').toString(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java new file mode 100644 index 000000000000..508880feb3c3 --- /dev/null +++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.contentcapture; + +import android.annotation.Nullable; + +/** + * Helpe class for this package. + */ +final class ContentCaptureHelper { + + // TODO(b/121044306): define a way to dynamically set them(for example, using settings?) + static final boolean VERBOSE = false; + static final boolean DEBUG = true; // STOPSHIP if not set to false + + /** + * Used to log text that could contain PII. + */ + @Nullable + public static String getSanitizedString(@Nullable CharSequence text) { + return text == null ? null : text.length() + "_chars"; + } + + private ContentCaptureHelper() { + throw new UnsupportedOperationException("contains only static methods"); + } +} diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 413f1a5a8955..b9017b365f90 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -15,6 +15,8 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; @@ -57,10 +59,6 @@ public final class ContentCaptureManager { */ private static final int SYNC_CALLS_TIMEOUT_MS = 5000; - // TODO(b/121044306): define a way to dynamically set them(for example, using settings?) - static final boolean VERBOSE = false; - static final boolean DEBUG = true; // STOPSHIP if not set to false - private final Object mLock = new Object(); @GuardedBy("mLock") @@ -191,13 +189,19 @@ public final class ContentCaptureManager { } /** - * Called by the ap to request the Content Capture service to remove user-data associated with + * Called by the app to request the Content Capture service to remove user-data associated with * some context. * * @param request object specifying what user data should be removed. */ public void removeUserData(@NonNull UserDataRemovalRequest request) { - //TODO(b/111276913): implement + Preconditions.checkNotNull(request); + + try { + mService.removeUserData(mContext.getUserId(), request); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @hide */ diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index b620ab1eb7c3..c425e7bd3700 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -15,8 +15,8 @@ */ package android.view.contentcapture; -import static android.view.contentcapture.ContentCaptureManager.DEBUG; -import static android.view.contentcapture.ContentCaptureManager.VERBOSE; +import static android.view.contentcapture.ContentCaptureHelper.DEBUG; +import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; import android.annotation.CallSuper; import android.annotation.IntDef; @@ -34,8 +34,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import dalvik.system.CloseGuard; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -148,9 +146,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) @interface FlushReason{} - - private final CloseGuard mCloseGuard = CloseGuard.get(); - private final Object mLock = new Object(); /** @@ -185,7 +180,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { @VisibleForTesting public ContentCaptureSession(@NonNull String id) { mId = Preconditions.checkNotNull(id); - mCloseGuard.open("destroy"); } /** @hide */ @@ -246,13 +240,11 @@ public abstract class ContentCaptureSession implements AutoCloseable { public final void destroy() { synchronized (mLock) { if (mDestroyed) { - Log.e(TAG, "destroy(" + mId + "): already destroyed"); + if (DEBUG) Log.d(TAG, "destroy(" + mId + "): already destroyed"); return; } mDestroyed = true; - mCloseGuard.close(); - // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote // id) and send it to the cache of batched commands if (VERBOSE) { @@ -288,18 +280,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { destroy(); } - @Override - protected void finalize() throws Throwable { - try { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - destroy(); - } finally { - super.finalize(); - } - } - /** * Notifies the Content Capture Service that a node has been added to the view structure. * @@ -352,14 +332,14 @@ public abstract class ContentCaptureSession implements AutoCloseable { * @throws IllegalArgumentException if {@code virtualIds} is empty */ public final void notifyViewsDisappeared(@NonNull AutofillId hostId, - @NonNull int[] virtualIds) { - Preconditions.checkArgument(!hostId.isVirtual(), "parent cannot be virtual"); + @NonNull long[] virtualIds) { + Preconditions.checkArgument(hostId.isNonVirtual(), "parent cannot be virtual"); Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty"); if (!isContentCaptureEnabled()) return; // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is // parcelized - for (int id : virtualIds) { + for (long id : virtualIds) { internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt())); } } @@ -405,9 +385,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { * * @throws IllegalArgumentException if the {@code parentId} is a virtual child id. */ - public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) { + public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, long virtualChildId) { Preconditions.checkNotNull(parentId); - Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children"); + Preconditions.checkArgument(parentId.isNonVirtual(), "virtual ids cannot have children"); return new AutofillId(parentId, virtualChildId, getIdAsInt()); } @@ -423,7 +403,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { */ @NonNull public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, - int virtualId) { + long virtualId) { return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt()); } diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index be9c00f04d99..51aea162f1db 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -19,6 +19,7 @@ package android.view.contentcapture; import android.content.ComponentName; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.UserDataRemovalRequest; import android.os.IBinder; import com.android.internal.os.IResultReceiver; @@ -56,4 +57,9 @@ oneway interface IContentCaptureManager { * provided {@code Bundle} with key "{@code EXTRA}". */ void getReceiverServiceComponentName(int userId, in IResultReceiver result); + + /** + * Requests the removal of user data for the provided {@code userId}. + */ + void removeUserData(int userId, in UserDataRemovalRequest request); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 103d7e6dc256..9e99c88da3ca 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -20,8 +20,9 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_START import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; -import static android.view.contentcapture.ContentCaptureManager.DEBUG; -import static android.view.contentcapture.ContentCaptureManager.VERBOSE; +import static android.view.contentcapture.ContentCaptureHelper.DEBUG; +import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; +import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -269,6 +270,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { final int eventType = event.getType(); + if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) { // TODO(b/120494182): comment when this could happen (dialogs?) Log.v(TAG, "handleSendEvent(" + getDebugState() + ", " @@ -276,12 +278,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { + "): session not started yet"); return; } - if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); + if (mDisabled.get()) { + // This happens when the event was queued in the handler before the sesison was ready, + // then handleSessionStarted() returned and set it as disabled - we need to drop it, + // otherwise it will keep triggering handleScheduleFlush() + if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled"); + return; + } if (mEvents == null) { if (VERBOSE) { - Log.v(TAG, "handleSendEvent(" + getDebugState() + ", " - + ContentCaptureEvent.getTypeAsString(eventType) - + "): creating buffer for " + MAX_BUFFER_SIZE + " events"); + Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events"); } mEvents = new ArrayList<>(MAX_BUFFER_SIZE); } @@ -296,8 +302,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED && lastEvent.getId().equals(event.getId())) { if (VERBOSE) { - Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = " - + event.getText()); + Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" + + getSanitizedString(event.getText())); } lastEvent.setText(event.getText()); addEvent = false; @@ -365,8 +371,20 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) { + if (VERBOSE) { + Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + + ", checkExisting=" + checkExisting); + } if (!handleHasStarted()) { - Log.v(TAG, "handleScheduleFlush(" + getDebugState() + "): session not started yet"); + if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet"); + return; + } + + if (mDisabled.get()) { + // Should not be called on this state, as handleSendEvent checks. + // But we rather add one if check and log than re-schedule and keep the session alive... + Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called " + + "when disabled. events=" + (mEvents == null ? null : mEvents.size())); return; } if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) { @@ -375,8 +393,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS; if (VERBOSE) { - Log.v(TAG, "handleScheduleFlush(" + getDebugState() - + ", reason=" + getflushReasonAsString(reason) + "): scheduled to flush in " + Log.v(TAG, "handleScheduleFlush(): scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); } mHandler.sendMessageDelayed( @@ -395,11 +412,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private void handleForceFlush(@FlushReason int reason) { if (mEvents == null) return; + if (mDisabled.get()) { + Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when " + + "disabled"); + return; + } + if (mDirectServiceInterface == null) { if (VERBOSE) { - Log.v(TAG, "handleForceFlush(" + getDebugState() - + ", reason=" + getflushReasonAsString(reason) - + "): hold your horses, client not ready: " + mEvents); + Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, " + + "client not ready: " + mEvents); } if (!mHandler.hasMessages(MSG_FLUSH)) { handleScheduleFlush(reason, /* checkExisting= */ false); @@ -410,8 +432,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int numberEvents = mEvents.size(); final String reasonString = getflushReasonAsString(reason); if (DEBUG) { - Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState() - + ". Reason: " + reasonString); + Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)); } // Logs reason, size, max size, idle timeout final String logRecord = "r=" + reasonString + " s=" + numberEvents @@ -592,7 +613,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { : "act:" + mComponentName.flattenToShortString(); } + @NonNull private String getDebugState() { - return getActivityName() + " (state=" + getStateAsString(mState) + ")"; + return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled=" + + mDisabled.get() + "]"; + } + + @NonNull + private String getDebugState(@FlushReason int reason) { + return getDebugState() + ", reason=" + getflushReasonAsString(reason); } } diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl new file mode 100644 index 000000000000..fbe47e08ea7c --- /dev/null +++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.contentcapture; + +parcelable UserDataRemovalRequest; diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java index 0261b70825a5..8ee63ef74685 100644 --- a/core/java/android/view/contentcapture/UserDataRemovalRequest.java +++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java @@ -17,10 +17,15 @@ package android.view.contentcapture; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityThread; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.util.IntArray; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; import java.util.List; /** @@ -29,8 +34,35 @@ import java.util.List; */ public final class UserDataRemovalRequest implements Parcelable { - private UserDataRemovalRequest(Builder builder) { - // TODO(b/111276913): implement + private final String mPackageName; + + private final boolean mForEverything; + private ArrayList<UriRequest> mUriRequests; + + private UserDataRemovalRequest(@NonNull Builder builder) { + mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName(); + mForEverything = builder.mForEverything; + if (builder.mUris != null) { + final int size = builder.mUris.size(); + mUriRequests = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mUriRequests.add(new UriRequest(builder.mUris.get(i), + builder.mRecursive.get(i) == 1)); + } + } + } + + private UserDataRemovalRequest(@NonNull Parcel parcel) { + mPackageName = parcel.readString(); + mForEverything = parcel.readBoolean(); + if (!mForEverything) { + final int size = parcel.readInt(); + mUriRequests = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mUriRequests.add(new UriRequest((Uri) parcel.readValue(null), + parcel.readBoolean())); + } + } } /** @@ -40,9 +72,7 @@ public final class UserDataRemovalRequest implements Parcelable { @SystemApi @NonNull public String getPackageName() { - // TODO(b/111276913): implement - // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app - return null; + return mPackageName; } /** @@ -52,8 +82,7 @@ public final class UserDataRemovalRequest implements Parcelable { */ @SystemApi public boolean isForEverything() { - // TODO(b/111276913): implement - return false; + return mForEverything; } /** @@ -64,8 +93,7 @@ public final class UserDataRemovalRequest implements Parcelable { @SystemApi @NonNull public List<UriRequest> getUriRequests() { - // TODO(b/111276913): implement - return null; + return mUriRequests; } /** @@ -73,6 +101,12 @@ public final class UserDataRemovalRequest implements Parcelable { */ public static final class Builder { + private boolean mForEverything; + private ArrayList<Uri> mUris; + private IntArray mRecursive; + + private boolean mDestroyed; + /** * Requests servive to remove all user data associated with the app's package. * @@ -80,7 +114,12 @@ public final class UserDataRemovalRequest implements Parcelable { */ @NonNull public Builder forEverything() { - // TODO(b/111276913): implement + throwIfDestroyed(); + if (mUris != null) { + throw new IllegalStateException("Already added Uris"); + } + + mForEverything = true; return this; } @@ -94,7 +133,19 @@ public final class UserDataRemovalRequest implements Parcelable { * @return this builder */ public Builder addUri(@NonNull Uri uri, boolean recursive) { - // TODO(b/111276913): implement + throwIfDestroyed(); + if (mForEverything) { + throw new IllegalStateException("Already is for everything"); + } + Preconditions.checkNotNull(uri); + + if (mUris == null) { + mUris = new ArrayList<>(); + mRecursive = new IntArray(); + } + + mUris.add(uri); + mRecursive.add(recursive ? 1 : 0); return this; } @@ -103,8 +154,16 @@ public final class UserDataRemovalRequest implements Parcelable { */ @NonNull public UserDataRemovalRequest build() { - // TODO(b/111276913): implement / unit test / check built / document exceptions - return null; + throwIfDestroyed(); + + Preconditions.checkState(mForEverything || mUris != null); + + mDestroyed = true; + return new UserDataRemovalRequest(this); + } + + private void throwIfDestroyed() { + Preconditions.checkState(!mDestroyed, "Already destroyed!"); } } @@ -115,7 +174,17 @@ public final class UserDataRemovalRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - // TODO(b/111276913): implement + parcel.writeString(mPackageName); + parcel.writeBoolean(mForEverything); + if (!mForEverything) { + final int size = mUriRequests.size(); + parcel.writeInt(size); + for (int i = 0; i < size; i++) { + final UriRequest request = mUriRequests.get(i); + parcel.writeValue(request.getUri()); + parcel.writeBoolean(request.isRecursive()); + } + } } public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR = @@ -123,8 +192,7 @@ public final class UserDataRemovalRequest implements Parcelable { @Override public UserDataRemovalRequest createFromParcel(Parcel parcel) { - // TODO(b/111276913): implement - return null; + return new UserDataRemovalRequest(parcel); } @Override diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index cbc946b773ca..0cabafa21b17 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -617,7 +617,7 @@ public final class ViewNode extends AssistStructure.ViewNode { } @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. - public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) { + public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) { mNode.mParentAutofillId = Preconditions.checkNotNull(parentId); mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId); } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index d09323d3f8ad..112653aa34e3 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -219,7 +219,7 @@ public interface InputMethod { @MainThread default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldPreRenderIme) { if (restarting) { restartInput(inputConnection, editorInfo); } else { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 10b99eca0fa8..0cb1800996c9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -27,6 +27,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; @@ -1881,7 +1882,15 @@ public final class InputMethodManager { /** * Notify the event when the user tapped or clicked the text view. + * + * @param view {@link View} which is being clicked. + * @see InputMethodService#onViewClicked(boolean) + * @deprecated The semantics of this method can never be defined well for composite {@link View} + * that works as a giant "Canvas", which can host its own UI hierarchy and sub focus + * state. {@link android.webkit.WebView} is a good example. Application / IME + * developers should not rely on this method. */ + @Deprecated public void viewClicked(View view) { // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); @@ -2481,14 +2490,58 @@ public final class InputMethodManager { * @param subtype A new input method subtype to switch. * @return true if the current subtype was successfully switched. When the specified subtype is * null, this method returns false. + * @deprecated If the calling process is an IME, use + * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which + * does not require any permission as long as the caller is the current IME. + * If the calling process is some privileged app that already has + * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just + * directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}. */ + @Deprecated @RequiresPermission(WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { + if (Process.myUid() == Process.SYSTEM_UID) { + Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because " + + "almost always it is a bug under multi-user / multi-profile environment. " + + "Consider directly interacting with InputMethodManagerService " + + "via LocalServices."); + return false; + } + if (subtype == null) { + // See the JavaDoc. This is how this method has worked. + return false; + } + final Context fallbackContext = ActivityThread.currentApplication(); + if (fallbackContext == null) { + return false; + } + if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + return false; + } + final ContentResolver contentResolver = fallbackContext.getContentResolver(); + final String imeId = Settings.Secure.getString(contentResolver, + Settings.Secure.DEFAULT_INPUT_METHOD); + if (ComponentName.unflattenFromString(imeId) == null) { + // Null or invalid IME ID format. + return false; + } + final List<InputMethodSubtype> enabledSubtypes; try { - return mService.setCurrentInputMethodSubtype(subtype); + enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true); } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + return false; } + final int numSubtypes = enabledSubtypes.size(); + for (int i = 0; i < numSubtypes; ++i) { + final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i); + if (enabledSubtype.equals(subtype)) { + Settings.Secure.putInt(contentResolver, + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode()); + return true; + } + } + return false; } /** @@ -2629,7 +2682,13 @@ public final class InputMethodManager { * * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to. * @param subtypes subtypes will be added as additional subtypes of the current input method. + * @deprecated For IMEs that have already implemented features like customizable/downloadable + * keyboard layouts/languages, please start migration to other approaches. One idea + * would be exposing only one unified {@link InputMethodSubtype} then implement + * IME's own language switching mechanism within that unified subtype. The support + * of "Additional Subtype" may be completely dropped in a future version of Android. */ + @Deprecated public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { try { mService.setAdditionalInputMethodSubtypes(imiId, subtypes); diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java index 355ff1d85e1f..f85952108f07 100644 --- a/core/java/android/view/inspector/InspectableProperty.java +++ b/core/java/android/view/inspector/InspectableProperty.java @@ -106,6 +106,7 @@ public @interface InspectableProperty { /** * One entry in an enumeration packed into a primitive {int}. * + * @see IntEnumMapping * @hide */ @Target({TYPE}) diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java new file mode 100644 index 000000000000..147bb46f2aa4 --- /dev/null +++ b/core/java/android/view/inspector/IntEnumMapping.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inspector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.SparseArray; + +import java.util.Objects; + +/** + * Maps the values of an {@code int} property to strings for properties that encode an enumeration. + * + * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper} + * for flag values packed into primitive {@code int} properties. + * + * This class is an immutable wrapper for {@link SparseArray}, and must be constructed by a + * {@link Builder}. + * + * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping) + */ +public final class IntEnumMapping { + private final SparseArray<String> mValues; + + /** + * Get the name for the given property value + * + * @param value The value of the property + * @return The name of the value in the enumeration, or null if no value is defined + */ + @Nullable + public String get(int value) { + return mValues.get(value); + } + + /** + * Create a new instance from a builder. + * + * This constructor is private, use {@link Builder#build()} instead. + * + * @param builder A builder to create from + */ + private IntEnumMapping(Builder builder) { + mValues = builder.mValues.clone(); + } + + /** + * A builder for {@link IntEnumMapping}. + */ + public static final class Builder { + @NonNull + private SparseArray<String> mValues; + private boolean mMustCloneValues = false; + + public Builder() { + mValues = new SparseArray<>(); + } + + /** + * Add a new enumerated value. + * + * @param name The string name of the enumeration value + * @param value The {@code int} value of the enumeration value + * @return This builder + */ + @NonNull + public Builder addValue(@NonNull String name, int value) { + // Save an allocation, only re-clone if the builder is used again after building + if (mMustCloneValues) { + mValues = mValues.clone(); + } + + mValues.put(value, Objects.requireNonNull(name)); + return this; + } + + /** + * Build a new {@link IntEnumMapping} from this builder. + * + * @return A new mapping + */ + @NonNull + public IntEnumMapping build() { + mMustCloneValues = true; + return new IntEnumMapping(this); + } + } +} diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java index 8f7dfd5c5144..2409081ca3a5 100644 --- a/core/java/android/view/inspector/IntFlagMapping.java +++ b/core/java/android/view/inspector/IntFlagMapping.java @@ -21,10 +21,11 @@ import android.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.Objects; import java.util.Set; /** - * Maps the values of an {@code int} property to arrays of string for properties that encode flags. + * Maps the values of an {@code int} property to sets of string for properties that encode flags. * * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper} * for flag values packed into primitive {@code int} properties. @@ -45,7 +46,7 @@ public final class IntFlagMapping { * Get an array of the names of enabled flags for a given property value. * * @param value The value of the property - * @return The names of the enabled flags + * @return The names of the enabled flags, empty if no flags enabled */ @NonNull public Set<String> get(int value) { @@ -136,7 +137,7 @@ public final class IntFlagMapping { private final int mMask; private Flag(@NonNull String name, int target, int mask) { - mName = name; + mName = Objects.requireNonNull(name); mTarget = target; mMask = mask; } diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java index e20582bf3ee4..00b18d172eed 100644 --- a/core/java/android/view/inspector/PropertyMapper.java +++ b/core/java/android/view/inspector/PropertyMapper.java @@ -18,7 +18,6 @@ package android.view.inspector; import android.annotation.AttrRes; import android.annotation.NonNull; -import android.util.SparseArray; /** * An interface for mapping the string names of inspectable properties to integer identifiers. @@ -155,14 +154,14 @@ public interface PropertyMapper { int mapIntEnum( @NonNull String name, @AttrRes int attributeId, - @NonNull SparseArray<String> mapping); + @NonNull IntEnumMapping mapping); /** * Map a string name to an integer ID for a flag set packed into an int property. * * @param name The name of the property * @param attributeId If the property is from an XML attribute, the resource ID of the property - * @param mapping A mapping from int to an array of strings + * @param mapping A mapping from int to a set of strings * @return An integer ID for the property * @throws PropertyConflictException If the property name is already mapped as another type. */ diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index 77cb4cd28763..4d917a1b1968 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -103,10 +103,9 @@ public final class ActionsSuggestionsHelper { final String modelName = String.format( Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion); final int hash = Objects.hash( - messages.stream() - .map(ConversationActions.Message::getText) - .collect(Collectors.toList()), - context.getPackageName()); + messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage), + context.getPackageName(), + System.currentTimeMillis()); return SelectionSessionLogger.SignatureParser.createSignature( SelectionSessionLogger.CLASSIFIER_ID, modelName, hash); } @@ -116,7 +115,7 @@ public final class ActionsSuggestionsHelper { private int mNextUserId = FIRST_NON_LOCAL_USER; private int encode(Person person) { - if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) { + if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) { return USER_LOCAL; } Integer result = mMapping.get(person); @@ -128,4 +127,8 @@ public final class ActionsSuggestionsHelper { return result; } } + + private static int hashMessage(ConversationActions.Message message) { + return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime()); + } } diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index f7c1a2640dc5..502181f633b6 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -109,9 +109,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_LOCAL = + public static final Person PERSON_USER_SELF = new Person.Builder() - .setKey("text-classifier-conversation-actions-local-user") + .setKey("text-classifier-conversation-actions-user-self") .build(); /** @@ -123,9 +123,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_REMOTE = + public static final Person PERSON_USER_OTHERS = new Person.Builder() - .setKey("text-classifier-conversation-actions-remote-user") + .setKey("text-classifier-conversation-actions-user-others") .build(); @Nullable @@ -235,10 +235,10 @@ public final class ConversationActions implements Parcelable { /** * Constructs a builder. * - * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * @param author the person that composed the message, use {@link #PERSON_USER_SELF} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use - * {@link #PERSON_USER_REMOTE} to represent a remote user. + * {@link #PERSON_USER_OTHERS} to represent a remote user. */ public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index ed862064be67..10c7adef28fd 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -73,9 +73,16 @@ public final class TextClassificationManager { /** * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}. * If this is null, this method returns a default text classifier (i.e. either the system text - * classifier if one exists, or a local text classifier running in this app.) + * classifier if one exists, or a local text classifier running in this process.) + * <p> + * Note that if system textclassifier is in use, requests will be sent to a textclassifier + * package provided from OEM. If you want to make sure the requests are handled in your own + * process, you should consider {@link #getLocalTextClassifier()} instead. However, the local + * textclassifier may return inferior results to those returned by the system + * textclassifier. * * @see #setTextClassifier(TextClassifier) + * @see #getLocalTextClassifier() */ @NonNull public TextClassifier getTextClassifier() { @@ -215,7 +222,13 @@ public final class TextClassificationManager { return TextClassifier.NO_OP; } - private TextClassifier getLocalTextClassifier() { + /** + * Returns a local textclassifier, which is running in this process. + * + * @see #getTextClassifier() + */ + @NonNull + public TextClassifier getLocalTextClassifier() { synchronized (mLock) { if (mLocalTextClassifier == null) { if (getSettings().isLocalTextClassifierEnabled()) { diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index b84f6f07e414..cd13cc0ec577 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -72,7 +72,7 @@ public final class TextClassifierEvent implements Parcelable { TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION, TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION, TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL, - TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY}) + TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED}) public @interface Type { // For custom event types, use range 1,000,000+. } @@ -121,7 +121,7 @@ public final class TextClassifierEvent implements Parcelable { @Category private final int mEventCategory; @Type private final int mEventType; - @Nullable private final String mEntityType; + @Nullable private final String[] mEntityTypes; @Nullable private final TextClassificationContext mEventContext; @Nullable private final String mResultId; private final int mEventIndex; @@ -139,11 +139,12 @@ public final class TextClassifierEvent implements Parcelable { // Language detection. @Nullable private final String mLanguage; + private final float mScore; private TextClassifierEvent( int eventCategory, int eventType, - String entityType, + String[] entityTypes, TextClassificationContext eventContext, String resultId, int eventIndex, @@ -154,10 +155,11 @@ public final class TextClassifierEvent implements Parcelable { int relativeSuggestedWordStartIndex, int relativeSuggestedWordEndIndex, int[] actionIndex, - String language) { + String language, + float score) { mEventCategory = eventCategory; mEventType = eventType; - mEntityType = entityType; + mEntityTypes = entityTypes; mEventContext = eventContext; mResultId = resultId; mEventIndex = eventIndex; @@ -169,6 +171,7 @@ public final class TextClassifierEvent implements Parcelable { mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex; mActionIndices = actionIndex; mLanguage = language; + mScore = score; } @Override @@ -180,7 +183,7 @@ public final class TextClassifierEvent implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mEventCategory); dest.writeInt(mEventType); - dest.writeString(mEntityType); + dest.writeStringArray(mEntityTypes); dest.writeParcelable(mEventContext, flags); dest.writeString(mResultId); dest.writeInt(mEventIndex); @@ -192,13 +195,14 @@ public final class TextClassifierEvent implements Parcelable { dest.writeInt(mRelativeSuggestedWordEndIndex); dest.writeIntArray(mActionIndices); dest.writeString(mLanguage); + dest.writeFloat(mScore); } private static TextClassifierEvent readFromParcel(Parcel in) { return new TextClassifierEvent( /* eventCategory= */ in.readInt(), /* eventType= */ in.readInt(), - /* entityType= */ in.readString(), + /* entityTypes=*/ in.readStringArray(), /* eventContext= */ in.readParcelable(null), /* resultId= */ in.readString(), /* eventIndex= */ in.readInt(), @@ -209,7 +213,8 @@ public final class TextClassifierEvent implements Parcelable { /* relativeSuggestedWordStartIndex= */ in.readInt(), /* relativeSuggestedWordEndIndex= */ in.readInt(), /* actionIndices= */ in.createIntArray(), - /* language= */ in.readString()); + /* language= */ in.readString(), + /* score= */ in.readFloat()); } /** @@ -229,11 +234,11 @@ public final class TextClassifierEvent implements Parcelable { } /** - * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. */ - @Nullable - public String getEntityType() { - return mEntityType; + @NonNull + public String[] getEntityTypes() { + return mEntityTypes; } /** @@ -327,13 +332,20 @@ public final class TextClassifierEvent implements Parcelable { } /** + * Returns the score of the suggestion. + */ + public float getScore() { + return mScore; + } + + /** * Builder to build a text classifier event. */ public static final class Builder { private final int mEventCategory; private final int mEventType; - @Nullable private String mEntityType; + private String[] mEntityTypes = new String[0]; @Nullable private TextClassificationContext mEventContext; @Nullable private String mResultId; private int mEventIndex; @@ -345,6 +357,7 @@ public final class TextClassifierEvent implements Parcelable { private int mRelativeSuggestedWordEndIndex; private int[] mActionIndices = new int[0]; @Nullable private String mLanguage; + private float mScore; /** * Creates a builder for building {@link TextClassifierEvent}s. @@ -358,11 +371,12 @@ public final class TextClassifierEvent implements Parcelable { } /** - * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. */ @NonNull - public Builder setEntityType(@Nullable String entityType) { - mEntityType = entityType; + public Builder setEntityTypes(@NonNull String... entityTypes) { + mEntityTypes = new String[entityTypes.length]; + System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length); return this; } @@ -478,6 +492,15 @@ public final class TextClassifierEvent implements Parcelable { } /** + * Sets the score of the suggestion. + */ + @NonNull + public Builder setScore(float score) { + mScore = score; + return this; + } + + /** * Builds and returns a text classifier event. */ @NonNull @@ -486,7 +509,7 @@ public final class TextClassifierEvent implements Parcelable { return new TextClassifierEvent( mEventCategory, mEventType, - mEntityType, + mEntityTypes, mEventContext, mResultId, mEventIndex, @@ -497,7 +520,8 @@ public final class TextClassifierEvent implements Parcelable { mRelativeSuggestedWordStartIndex, mRelativeSuggestedWordEndIndex, mActionIndices, - mLanguage); + mLanguage, + mScore); } // TODO: Add build(boolean validate). } @@ -507,7 +531,7 @@ public final class TextClassifierEvent implements Parcelable { StringBuilder out = new StringBuilder(128); out.append("TextClassifierEvent{"); out.append("mEventCategory=").append(mEventCategory); - out.append(", mEventType=").append(mEventType); + out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes)); out.append(", mEventContext=").append(mEventContext); out.append(", mResultId=").append(mResultId); out.append(", mEventIndex=").append(mEventIndex); @@ -519,6 +543,7 @@ public final class TextClassifierEvent implements Parcelable { out.append(", mRelativeSuggestedWordEndIndex=").append(mRelativeSuggestedWordEndIndex); out.append(", mActionIndices=").append(Arrays.toString(mActionIndices)); out.append(", mLanguage=").append(mLanguage); + out.append(", mScore=").append(mScore); out.append("}"); return out.toString(); } diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java index 439e594cc8fe..5563dfc2eee5 100644 --- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java +++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java @@ -15,12 +15,15 @@ */ package android.view.textclassifier; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION; import android.metrics.LogMaker; @@ -60,16 +63,30 @@ public final class TextClassifierEventTronLogger { return; } final LogMaker log = new LogMaker(category) - .setType(getLogType(event)) - .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId()) + .setSubtype(getLogType(event)) + .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId()) .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime()) .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL, SelectionSessionLogger.SignatureParser.getModelName(event.getResultId())) - .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType()); + .addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScore()); + + String[] entityTypes = event.getEntityTypes(); + // TRON does not support a field of list type, and thus workaround by store them + // in three separate fields. This is no longer an issue once we have moved to Westworld. + if (entityTypes.length >= 1) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]); + } + if (entityTypes.length >= 2) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]); + } + if (entityTypes.length >= 3) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]); + } TextClassificationContext eventContext = event.getEventContext(); if (eventContext != null) { - log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType()); - log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion()); + log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType()); + log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION, + eventContext.getWidgetVersion()); log.setPackageName(eventContext.getPackageName()); } mMetricsLogger.write(log); @@ -94,6 +111,8 @@ public final class TextClassifierEventTronLogger { return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN; case TextClassifierEvent.TYPE_MANUAL_REPLY: return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY; + case TextClassifierEvent.TYPE_ACTIONS_GENERATED: + return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED; default: return MetricsEvent.VIEW_UNKNOWN; } @@ -127,14 +146,22 @@ public final class TextClassifierEventTronLogger { if (!Log.ENABLE_FULL_LOGGING) { return; } - final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID)); + final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID)); final String categoryName = toCategoryName(log.getCategory()); - final String eventName = toEventName(log.getType()); - final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE)); + final String eventName = toEventName(log.getSubtype()); + final String widgetType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE)); final String widgetVersion = - String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION)); + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION)); final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL)); - final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE)); + final String firstEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE)); + final String secondEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE)); + final String thirdEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE)); + final String score = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE)); StringBuilder builder = new StringBuilder(); builder.append("writeEvent: "); @@ -144,7 +171,10 @@ public final class TextClassifierEventTronLogger { builder.append(", widgetType=").append(widgetType); builder.append(", widgetVersion=").append(widgetVersion); builder.append(", model=").append(model); - builder.append(", entityType=").append(entityType); + builder.append(", firstEntityType=").append(firstEntityType); + builder.append(", secondEntityType=").append(secondEntityType); + builder.append(", thirdEntityType=").append(thirdEntityType); + builder.append(", score=").append(score); Log.v(TAG, builder.toString()); } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 9ab963e372b7..a5b7c621be38 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -78,6 +78,9 @@ import java.util.concurrent.TimeUnit; */ public final class TextClassifierImpl implements TextClassifier { + /** @hide */ + public static final String ACTIONS_INTENTS = "actions-intents"; + private static final String LOG_TAG = DEFAULT_LOG_TAG; private static final boolean DEBUG = false; @@ -567,6 +570,7 @@ public final class TextClassifierImpl implements TextClassifier { // TODO: Make this configurable. final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f; boolean isPrimaryAction = true; + final ArrayList<Intent> sourceIntents = new ArrayList<>(); for (LabeledIntent labeledIntent : IntentFactory.create( mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold), referenceTime, highestScoringResult)) { @@ -586,9 +590,15 @@ public final class TextClassifierImpl implements TextClassifier { isPrimaryAction = false; } builder.addAction(action); + sourceIntents.add(labeledIntent.getIntent()); } - return builder.setId(createId(text, start, end)).build(); + final Bundle extras = new Bundle(); + extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents); + + return builder.setId(createId(text, start, end)) + .setExtras(extras) + .build(); } private boolean isForeignText(String text, float threshold) { diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 29b3b3cff044..de1f3df61462 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -150,7 +150,8 @@ public class WebViewZygote { } try { - sZygote = Process.zygoteProcess.startChildZygote( + String abi = sPackage.applicationInfo.primaryCpuAbi; + sZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.WebViewZygoteInit", "webview_zygote", Process.WEBVIEW_ZYGOTE_UID, @@ -158,39 +159,40 @@ public class WebViewZygote { null, // gids 0, // runtimeFlags "webview_zygote", // seInfo - sPackage.applicationInfo.primaryCpuAbi, // abi + abi, // abi TextUtils.join(",", Build.SUPPORTED_ABIS), null, // instructionSet Process.FIRST_ISOLATED_UID, Process.LAST_ISOLATED_UID); - - // All the work below is usually done by LoadedApk, but the zygote can't talk to - // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so - // doesn't have an ActivityThread and can't use Binder. - // Instead, figure out the paths here, in the system server where we have access to - // the package manager. Reuse the logic from LoadedApk to determine the correct - // paths and pass them to the zygote as strings. - final List<String> zipPaths = new ArrayList<>(10); - final List<String> libPaths = new ArrayList<>(10); - LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); - final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); - final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); - - // In the case where the ApplicationInfo has been modified by the stub WebView, - // we need to use the original ApplicationInfo to determine what the original classpath - // would have been to use as a cache key. - LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); - final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress()); - Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); - sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, - Build.SUPPORTED_ABIS[0]); + if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) { + // No stub WebView is involved here, so we can preload the package the "clean" way + // using the ApplicationInfo. + sZygote.preloadApp(sPackage.applicationInfo, abi); + } else { + // Legacy path to support the stub WebView. + // Reuse the logic from LoadedApk to determine the correct paths and pass them to + // the zygote as strings. + final List<String> zipPaths = new ArrayList<>(10); + final List<String> libPaths = new ArrayList<>(10); + LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); + final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); + final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : + TextUtils.join(File.pathSeparator, zipPaths); + + String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); + + // Use the original ApplicationInfo to determine what the original classpath would + // have been to use as a cache key. + LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); + final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : + TextUtils.join(File.pathSeparator, zipPaths); + + Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); + sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, + Build.SUPPORTED_ABIS[0]); + } } catch (Exception e) { Log.e(LOGTAG, "Error connecting to webview zygote", e); stopZygoteLocked(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1085e5dadb42..780fe8d821d6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5117,7 +5117,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_scrollHorizontally * @see #setHorizontallyScrolling(boolean) */ - public final boolean isHorizontallyScrolling() { + public final boolean isHorizontallyScrollable() { return mHorizontallyScrolling; } diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java index 7c371cb18878..d0102a72e703 100644 --- a/core/java/com/android/internal/app/AssistUtils.java +++ b/core/java/com/android/internal/app/AssistUtils.java @@ -17,13 +17,10 @@ package com.android.internal.app; import android.annotation.NonNull; -import android.app.SearchManager; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -31,8 +28,6 @@ import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; -import com.android.internal.R; - import java.util.ArrayList; import java.util.Set; @@ -44,14 +39,6 @@ public class AssistUtils { private static final String TAG = "AssistUtils"; - /** - * Sentinel value for "no default assistant specified." - * - * Empty string is already used to represent an explicit setting of No Assistant. null cannot - * be used because we can't represent a null value in XML. - */ - private static final String UNSET = "#+UNSET"; - private final Context mContext; private final IVoiceInteractionManagerService mVoiceInteractionManagerService; @@ -186,37 +173,9 @@ public class AssistUtils { Settings.Secure.ASSISTANT, userId); if (setting != null) { return ComponentName.unflattenFromString(setting); - } - - final String defaultSetting = mContext.getResources().getString( - R.string.config_defaultAssistantComponentName); - if (defaultSetting != null && !defaultSetting.equals(UNSET)) { - return ComponentName.unflattenFromString(defaultSetting); - } - - // Fallback to keep backward compatible behavior when there is no user setting. - if (activeServiceSupportsAssistGesture()) { - return getActiveServiceComponentName(); - } - - if (UNSET.equals(defaultSetting)) { + } else { return null; } - - final SearchManager searchManager = - (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); - if (searchManager == null) { - return null; - } - final Intent intent = searchManager.getAssistIntent(false); - PackageManager pm = mContext.getPackageManager(); - ResolveInfo info = pm.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY, - userId); - if (info != null) { - return new ComponentName(info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - } - return null; } public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index b4d8322c7552..803462d59fad 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -18,6 +18,10 @@ package com.android.internal.app; import android.app.Activity; import android.app.ActivityManager; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -98,12 +102,28 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; + + /** + * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true, + * {@link AppPredictionManager} will be queried for direct share targets. + */ + // TODO(b/123089490): Replace with system flag + private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false; + // TODO(b/123088566) Share these in a better way. + private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; + private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; + public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; + private AppPredictor mAppPredictor; + private AppPredictor.Callback mAppPredictorCallback; + /** * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of * binding to every ChooserTargetService implementation. */ // TODO(b/121287573): Replace with a system flag (setprop?) - private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = false; + private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true; + private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true; + // TODO(b/121287224): Re-evaluate this limit private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; @@ -136,6 +156,7 @@ public class ChooserActivity extends ResolverActivity { private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3; + private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4; private final Handler mChooserHandler = new Handler() { @Override @@ -182,6 +203,9 @@ public class ChooserActivity extends ResolverActivity { mChooserListAdapter.addServiceResults(resultInfo.originalTarget, resultInfo.resultTargets); } + break; + + case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED: sendVoiceChoicesIfNeeded(); mChooserListAdapter.setShowServiceTargets(true); break; @@ -303,6 +327,35 @@ public class ChooserActivity extends ResolverActivity { mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost); + + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + final IntentFilter filter = getTargetIntentFilter(); + Bundle extras = new Bundle(); + extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter); + AppPredictionManager appPredictionManager = + getSystemService(AppPredictionManager.class); + mAppPredictor = appPredictionManager.createAppPredictionSession( + new AppPredictionContext.Builder(this) + .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT) + .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) + .setExtras(extras) + .build()); + mAppPredictorCallback = resultList -> { + final List<DisplayResolveInfo> driList = + getDisplayResolveInfos(mChooserListAdapter); + final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos = + new ArrayList<>(); + for (AppTarget appTarget : resultList) { + shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo( + appTarget.getShortcutInfo(), + new ComponentName( + appTarget.getPackageName(), appTarget.getClassName()))); + } + sendShareShortcutInfoList(shareShortcutInfos, driList); + }; + mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback); + } + if (DEBUG) { Log.d(TAG, "System Time Cost is " + systemCost); } @@ -333,6 +386,10 @@ public class ChooserActivity extends ResolverActivity { } unbindRemainingServices(); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback); + mAppPredictor.destroy(); + } } @Override @@ -507,6 +564,7 @@ public class ChooserActivity extends ResolverActivity { void queryTargetServices(ChooserListAdapter adapter) { final PackageManager pm = getPackageManager(); + ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class); int targetsToQuery = 0; for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); @@ -516,6 +574,11 @@ public class ChooserActivity extends ResolverActivity { continue; } final ActivityInfo ai = dri.getResolveInfo().activityInfo; + if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS + && sm.hasShareTargets(ai.packageName)) { + // Share targets will be queried from ShortcutManager + continue; + } final Bundle md = ai.metaData; final String serviceName = md != null ? convertServiceName(ai.packageName, md.getString(ChooserTargetService.META_DATA_NAME)) : null; @@ -594,15 +657,10 @@ public class ChooserActivity extends ResolverActivity { } } - private void queryDirectShareTargets(ChooserListAdapter adapter) { - final IntentFilter filter = getTargetIntentFilter(); - if (filter == null) { - return; - } - + private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) { // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo // and use the old code path. This Ugliness should go away when Sharesheet is refactored. - final List<DisplayResolveInfo> driList = new ArrayList<>(); + List<DisplayResolveInfo> driList = new ArrayList<>(); int targetsToQuery = 0; for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) { final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); @@ -622,32 +680,57 @@ public class ChooserActivity extends ResolverActivity { break; } } + return driList; + } + + private void queryDirectShareTargets(ChooserListAdapter adapter) { + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.requestPredictionUpdate(); + return; + } + final IntentFilter filter = getTargetIntentFilter(); + if (filter == null) { + return; + } + final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter); AsyncTask.execute(() -> { ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter); + sendShareShortcutInfoList(resultList, driList); + }); + } - // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path - // for direct share targets. After ShareSheet is refactored we should use the - // ShareShortcutInfos directly. - for (int i = 0; i < driList.size(); i++) { - List<ChooserTarget> chooserTargets = new ArrayList<>(); - for (int j = 0; j < resultList.size(); j++) { - if (driList.get(i).getResolvedComponentName().equals( + private void sendShareShortcutInfoList( + List<ShortcutManager.ShareShortcutInfo> resultList, + List<DisplayResolveInfo> driList) { + // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path + // for direct share targets. After ShareSheet is refactored we should use the + // ShareShortcutInfos directly. + boolean resultMessageSent = false; + for (int i = 0; i < driList.size(); i++) { + List<ChooserTarget> chooserTargets = new ArrayList<>(); + for (int j = 0; j < resultList.size(); j++) { + if (driList.get(i).getResolvedComponentName().equals( resultList.get(j).getTargetComponent())) { - chooserTargets.add(convertToChooserTarget(resultList.get(j))); - } + chooserTargets.add(convertToChooserTarget(resultList.get(j))); } - if (chooserTargets.isEmpty()) { - continue; - } - - final Message msg = Message.obtain(); - msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; - msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); - mChooserHandler.sendMessage(msg); } - }); + if (chooserTargets.isEmpty()) { + continue; + } + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; + msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); + mChooserHandler.sendMessage(msg); + resultMessageSent = true; + } + + if (resultMessageSent) { + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; + mChooserHandler.sendMessage(msg); + } } private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) { @@ -704,6 +787,7 @@ public class ChooserActivity extends ResolverActivity { // Do nothing. We'll send the voice stuff ourselves. } + // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor void updateModelAndChooserCounts(TargetInfo info) { if (info != null) { final ResolveInfo ri = info.getResolveInfo(); @@ -1178,13 +1262,17 @@ public class ChooserActivity extends ResolverActivity { mTargetsNeedPruning = true; } } + if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) { if (DEBUG) { Log.d(TAG, "querying direct share targets from ShortcutManager"); } queryDirectShareTargets(this); - } else { - if (DEBUG) Log.d(TAG, "List built querying services"); + } + if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) { + if (DEBUG) { + Log.d(TAG, "List built querying services"); + } queryTargetServices(this); } } diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java index c093fe512186..2ac0e4de58ac 100644 --- a/core/java/com/android/internal/app/ColorDisplayController.java +++ b/core/java/com/android/internal/app/ColorDisplayController.java @@ -16,28 +16,20 @@ package com.android.internal.app; -import android.annotation.IntDef; import android.annotation.NonNull; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.metrics.LogMaker; +import android.hardware.display.ColorDisplayManager; +import android.hardware.display.ColorDisplayManager.AutoMode; +import android.hardware.display.ColorDisplayManager.ColorMode; import android.net.Uri; import android.os.Handler; import android.os.Looper; -import android.os.SystemProperties; import android.provider.Settings.Secure; -import android.provider.Settings.System; import android.util.Slog; -import com.android.internal.R; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.LocalDateTime; import java.time.LocalTime; /** @@ -51,67 +43,12 @@ public final class ColorDisplayController { private static final String TAG = "ColorDisplayController"; private static final boolean DEBUG = false; - @Retention(RetentionPolicy.SOURCE) - @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT }) - public @interface AutoMode {} - - /** - * Auto mode value to prevent Night display from being automatically activated. It can still - * be activated manually via {@link #setActivated(boolean)}. - * - * @see #setAutoMode(int) - */ - public static final int AUTO_MODE_DISABLED = 0; - /** - * Auto mode value to automatically activate Night display at a specific start and end time. - * - * @see #setAutoMode(int) - * @see #setCustomStartTime(LocalTime) - * @see #setCustomEndTime(LocalTime) - */ - public static final int AUTO_MODE_CUSTOM = 1; - /** - * Auto mode value to automatically activate Night display from sunset to sunrise. - * - * @see #setAutoMode(int) - */ - public static final int AUTO_MODE_TWILIGHT = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC }) - public @interface ColorMode {} - - /** - * Color mode with natural colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_NATURAL = 0; - /** - * Color mode with boosted colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_BOOSTED = 1; - /** - * Color mode with saturated colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_SATURATED = 2; - /** - * Color mode with automatic colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_AUTOMATIC = 3; - private final Context mContext; private final int mUserId; + private final ColorDisplayManager mColorDisplayManager; private ContentObserver mContentObserver; private Callback mCallback; - private MetricsLogger mMetricsLogger; public ColorDisplayController(@NonNull Context context) { this(context, ActivityManager.getCurrentUser()); @@ -120,14 +57,14 @@ public final class ColorDisplayController { public ColorDisplayController(@NonNull Context context, int userId) { mContext = context.getApplicationContext(); mUserId = userId; + mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class); } /** * Returns {@code true} when Night display is activated (the display is tinted red). */ public boolean isActivated() { - return Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1; + return mColorDisplayManager.isNightDisplayActivated(); } /** @@ -137,40 +74,16 @@ public final class ColorDisplayController { * @return {@code true} if the activated value was set successfully */ public boolean setActivated(boolean activated) { - if (isActivated() != activated) { - Secure.putStringForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, - LocalDateTime.now().toString(), - mUserId); - } - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId); + return mColorDisplayManager.setNightDisplayActivated(activated); } /** * Returns the current auto mode value controlling when Night display will be automatically - * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or - * {@link #AUTO_MODE_TWILIGHT}. + * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}. */ public @AutoMode int getAutoMode() { - int autoMode = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId); - if (autoMode == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE); - } - autoMode = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayAutoMode); - } - - if (autoMode != AUTO_MODE_DISABLED - && autoMode != AUTO_MODE_CUSTOM - && autoMode != AUTO_MODE_TWILIGHT) { - Slog.e(TAG, "Invalid autoMode: " + autoMode); - autoMode = AUTO_MODE_DISABLED; - } - - return autoMode; + return mColorDisplayManager.getNightDisplayAutoMode(); } /** @@ -178,138 +91,64 @@ public final class ColorDisplayController { * never been set. */ public int getAutoModeRaw() { - return Secure.getIntForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, - -1, mUserId); + return mColorDisplayManager.getNightDisplayAutoModeRaw(); } /** * Sets the current auto mode value controlling when Night display will be automatically - * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or - * {@link #AUTO_MODE_TWILIGHT}. + * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}. * * @param autoMode the new auto mode to use * @return {@code true} if new auto mode was set successfully */ public boolean setAutoMode(@AutoMode int autoMode) { - if (autoMode != AUTO_MODE_DISABLED - && autoMode != AUTO_MODE_CUSTOM - && autoMode != AUTO_MODE_TWILIGHT) { - throw new IllegalArgumentException("Invalid autoMode: " + autoMode); - } - - if (getAutoMode() != autoMode) { - Secure.putStringForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, - null, - mUserId); - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(autoMode)); - } - - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId); + return mColorDisplayManager.setNightDisplayAutoMode(autoMode); } /** - * Returns the local time when Night display will be automatically activated when using - * {@link #AUTO_MODE_CUSTOM}. + * Returns the local time when Night display will be automatically activated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. */ public @NonNull LocalTime getCustomStartTime() { - int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId); - if (startTimeValue == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME); - } - startTimeValue = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayCustomStartTime); - } - - return LocalTime.ofSecondOfDay(startTimeValue / 1000); + return mColorDisplayManager.getNightDisplayCustomStartTime(); } /** - * Sets the local time when Night display will be automatically activated when using - * {@link #AUTO_MODE_CUSTOM}. + * Sets the local time when Night display will be automatically activated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. * * @param startTime the local time to automatically activate Night display * @return {@code true} if the new custom start time was set successfully */ public boolean setCustomStartTime(@NonNull LocalTime startTime) { - if (startTime == null) { - throw new IllegalArgumentException("startTime cannot be null"); - } - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(0)); - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId); + return mColorDisplayManager.setNightDisplayCustomStartTime(startTime); } /** - * Returns the local time when Night display will be automatically deactivated when using - * {@link #AUTO_MODE_CUSTOM}. + * Returns the local time when Night display will be automatically deactivated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. */ public @NonNull LocalTime getCustomEndTime() { - int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId); - if (endTimeValue == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME); - } - endTimeValue = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayCustomEndTime); - } - - return LocalTime.ofSecondOfDay(endTimeValue / 1000); + return mColorDisplayManager.getNightDisplayCustomEndTime(); } /** - * Sets the local time when Night display will be automatically deactivated when using - * {@link #AUTO_MODE_CUSTOM}. + * Sets the local time when Night display will be automatically deactivated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. * * @param endTime the local time to automatically deactivate Night display * @return {@code true} if the new custom end time was set successfully */ public boolean setCustomEndTime(@NonNull LocalTime endTime) { - if (endTime == null) { - throw new IllegalArgumentException("endTime cannot be null"); - } - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(1)); - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId); + return mColorDisplayManager.setNightDisplayCustomEndTime(endTime); } /** * Returns the color temperature (in Kelvin) to tint the display when activated. */ public int getColorTemperature() { - int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId); - if (colorTemperature == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE); - } - colorTemperature = getDefaultColorTemperature(); - } - final int minimumTemperature = getMinimumColorTemperature(); - final int maximumTemperature = getMaximumColorTemperature(); - if (colorTemperature < minimumTemperature) { - colorTemperature = minimumTemperature; - } else if (colorTemperature > maximumTemperature) { - colorTemperature = maximumTemperature; - } - - return colorTemperature; + return mColorDisplayManager.getNightDisplayColorTemperature(); } /** @@ -319,79 +158,14 @@ public final class ColorDisplayController { * @return {@code true} if new temperature was set successfully. */ public boolean setColorTemperature(int colorTemperature) { - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId); - } - - /** - * Get the current color mode from system properties, or return -1. - * - * See com.android.server.display.DisplayTransformManager. - */ - private @ColorMode int getCurrentColorModeFromSystemProperties() { - final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0); - if (displayColorSetting == 0) { - return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation")) - ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED; - } else if (displayColorSetting == 1) { - return COLOR_MODE_SATURATED; - } else if (displayColorSetting == 2) { - return COLOR_MODE_AUTOMATIC; - } else { - return -1; - } - } - - private boolean isColorModeAvailable(@ColorMode int colorMode) { - final int[] availableColorModes = mContext.getResources().getIntArray( - R.array.config_availableColorModes); - if (availableColorModes != null) { - for (int mode : availableColorModes) { - if (mode == colorMode) { - return true; - } - } - } - return false; + return mColorDisplayManager.setNightDisplayColorTemperature(colorTemperature); } /** * Get the current color mode. */ public int getColorMode() { - if (getAccessibilityTransformActivated()) { - if (isColorModeAvailable(COLOR_MODE_SATURATED)) { - return COLOR_MODE_SATURATED; - } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { - return COLOR_MODE_AUTOMATIC; - } - } - - int colorMode = System.getIntForUser(mContext.getContentResolver(), - System.DISPLAY_COLOR_MODE, -1, mUserId); - if (colorMode == -1) { - // There might be a system property controlling color mode that we need to respect; if - // not, this will set a suitable default. - colorMode = getCurrentColorModeFromSystemProperties(); - } - - // This happens when a color mode is no longer available (e.g., after system update or B&R) - // or the device does not support any color mode. - if (!isColorModeAvailable(colorMode)) { - if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) { - colorMode = COLOR_MODE_NATURAL; - } else if (colorMode == COLOR_MODE_SATURATED - && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { - colorMode = COLOR_MODE_AUTOMATIC; - } else if (colorMode == COLOR_MODE_AUTOMATIC - && isColorModeAvailable(COLOR_MODE_SATURATED)) { - colorMode = COLOR_MODE_SATURATED; - } else { - colorMode = -1; - } - } - - return colorMode; + return mColorDisplayManager.getColorMode(); } /** @@ -400,47 +174,21 @@ public final class ColorDisplayController { * @param colorMode the color mode */ public void setColorMode(@ColorMode int colorMode) { - if (!isColorModeAvailable(colorMode)) { - throw new IllegalArgumentException("Invalid colorMode: " + colorMode); - } - System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode, - mUserId); + mColorDisplayManager.setColorMode(colorMode); } /** * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated. */ public int getMinimumColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureMin); + return ColorDisplayManager.getMinimumColorTemperature(mContext); } /** * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated. */ public int getMaximumColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureMax); - } - - /** - * Returns the default color temperature (in Kelvin) to tint the display when activated. - */ - public int getDefaultColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureDefault); - } - - /** - * Returns true if any Accessibility color transforms are enabled. - */ - public boolean getAccessibilityTransformActivated() { - final ContentResolver cr = mContext.getContentResolver(); - return - Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, - 0, mUserId) == 1 - || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, - 0, mUserId) == 1; + return ColorDisplayManager.getMaximumColorTemperature(mContext); } private void onSettingChanged(@NonNull String setting) { @@ -465,13 +213,6 @@ public final class ColorDisplayController { case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: mCallback.onColorTemperatureChanged(getColorTemperature()); break; - case System.DISPLAY_COLOR_MODE: - mCallback.onDisplayColorModeChanged(getColorMode()); - break; - case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED: - case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED: - mCallback.onAccessibilityTransformChanged(getAccessibilityTransformActivated()); - break; } } } @@ -514,25 +255,10 @@ public final class ColorDisplayController { false /* notifyForDescendants */, mContentObserver, mUserId); cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE), false /* notifyForDescendants */, mContentObserver, mUserId); - cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE), - false /* notifyForDecendants */, mContentObserver, mUserId); - cr.registerContentObserver( - Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED), - false /* notifyForDecendants */, mContentObserver, mUserId); - cr.registerContentObserver( - Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED), - false /* notifyForDecendants */, mContentObserver, mUserId); } } } - private MetricsLogger getMetricsLogger() { - if (mMetricsLogger == null) { - mMetricsLogger = new MetricsLogger(); - } - return mMetricsLogger; - } - /** * Callback invoked whenever the Night display settings are changed. */ @@ -568,19 +294,5 @@ public final class ColorDisplayController { * @param colorTemperature the color temperature to tint the screen */ default void onColorTemperatureChanged(int colorTemperature) {} - - /** - * Callback invoked when the color mode changes. - * - * @param displayColorMode the color mode - */ - default void onDisplayColorModeChanged(int displayColorMode) {} - - /** - * Callback invoked when Accessibility color transforms change. - * - * @param state the state Accessibility color transforms (true of active) - */ - default void onAccessibilityTransformChanged(boolean state) {} } } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 514ff76372a9..d7514d1fe26c 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -154,4 +154,7 @@ interface IBatteryStats { oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info); oneway void noteModemControllerActivity(in ModemActivityInfo info); oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info); + + /** {@hide} */ + boolean setChargingStateUpdateDelayMillis(int delay); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 5088ccae5c1f..b85488ffe70c 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -151,4 +151,19 @@ interface IVoiceInteractionManagerService { */ void getActiveServiceSupportedActions(in List<String> voiceActions, in IVoiceActionCheckCallback callback); + + /** + * Sets the transcribed voice to the given string. + */ + void setTranscription(String transcription); + + /** + * Indicates that the transcription session is finished. + */ + void clearTranscription(boolean immediate); + + /** + * Sets the voice state indication based upon the given value. + */ + void setVoiceState(int state); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl index 87749d26e4a0..674ad5b4ab67 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl @@ -26,4 +26,20 @@ * Called when a voice session is hidden. */ void onVoiceSessionHidden(); + + /** + * Called when voice assistant transcription has been updated to the given string. + */ + void onTranscriptionUpdate(in String transcription); + + /** + * Called when voice transcription is completed. + */ + void onTranscriptionComplete(in boolean immediate); + + /** + * Called when the voice assistant's state has changed. Values are from + * VoiceInteractionService's VOICE_STATE* constants. + */ + void onVoiceStateChange(in int state); }
\ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 7600dc9be447..8978496073e5 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -100,6 +100,7 @@ public final class InputMethodPrivilegedOperations { * @param backDisposition disposition flags * @see android.inputmethodservice.InputMethodService#IME_ACTIVE * @see android.inputmethodservice.InputMethodService#IME_VISIBLE + * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING */ diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 9bacf9b6c2b9..f8483461c2d2 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -64,6 +64,9 @@ public class NetworkStatsFactory { private boolean mUseBpfStats; + // A persistent Snapshot since device start for eBPF stats + private final NetworkStats mPersistSnapshot; + // TODO: only do adjustments in NetworkStatsService and remove this. /** * (Stacked interface) -> (base interface) association for all connected ifaces since boot. @@ -135,6 +138,7 @@ public class NetworkStatsFactory { mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); mUseBpfStats = useBpfStats; + mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); } public NetworkStats readBpfNetworkStatsDev() throws IOException { @@ -268,6 +272,7 @@ public class NetworkStatsFactory { return stats; } + // TODO: delete the lastStats parameter private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats) throws IOException { if (USE_NATIVE_PARSING) { @@ -278,16 +283,28 @@ public class NetworkStatsFactory { } else { stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } - if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, - limitIfaces, limitTag, mUseBpfStats) != 0) { - throw new IOException("Failed to parse network stats"); - } - if (SANITY_CHECK_NATIVE) { - final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, - limitIfaces, limitTag); - assertEquals(javaStats, stats); + if (mUseBpfStats) { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL, + null, TAG_ALL, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime()); + mPersistSnapshot.combineAllValues(stats); + NetworkStats result = mPersistSnapshot.clone(); + result.filter(limitUid, limitIfaces, limitTag); + return result; + } else { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, + limitIfaces, limitTag, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + if (SANITY_CHECK_NATIVE) { + final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, + limitIfaces, limitTag); + assertEquals(javaStats, stats); + } + return stats; } - return stats; } else { return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag); } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index fd03b3f16348..da8605e645b4 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -104,6 +105,7 @@ public class VpnConfig implements Parcelable { public boolean allowIPv4; public boolean allowIPv6; public Network[] underlyingNetworks; + public ProxyInfo proxyInfo; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -164,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); + out.writeParcelable(proxyInfo, flags); } public static final Parcelable.Creator<VpnConfig> CREATOR = @@ -189,6 +192,7 @@ public class VpnConfig implements Parcelable { config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); + config.proxyInfo = in.readParcelable(null); return config; } @@ -220,6 +224,7 @@ public class VpnConfig implements Parcelable { .append(", allowIPv4=").append(allowIPv4) .append(", allowIPv6=").append(allowIPv6) .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks)) + .append(", proxyInfo=").append(proxyInfo.toString()) .append("}") .toString(); } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index a676dacb0c49..b1a412871bd2 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -32,11 +32,11 @@ public class VpnInfo implements Parcelable { @Override public String toString() { - return "VpnInfo{" + - "ownerUid=" + ownerUid + - ", vpnIface='" + vpnIface + '\'' + - ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + - '}'; + return "VpnInfo{" + + "ownerUid=" + ownerUid + + ", vpnIface='" + vpnIface + '\'' + + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + + '}'; } @Override diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 534361e13c7d..c6afee24cfb5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13395,11 +13395,22 @@ public class BatteryStatsImpl extends BatteryStats { mResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS), false /* notifyForDescendants */, this); + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY), + false /* notifyForDescendants */, this); updateConstants(); } @Override public void onChange(boolean selfChange, Uri uri) { + if (uri.equals( + Settings.Global.getUriFor( + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) { + synchronized (BatteryStatsImpl.this) { + updateBatteryChargedDelayMsLocked(); + } + return; + } updateConstants(); } @@ -13443,12 +13454,21 @@ public class BatteryStatsImpl extends BatteryStats { DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; - BATTERY_CHARGED_DELAY_MS = mParser.getInt( - KEY_BATTERY_CHARGED_DELAY_MS, - DEFAULT_BATTERY_CHARGED_DELAY_MS); + updateBatteryChargedDelayMsLocked(); } } + private void updateBatteryChargedDelayMsLocked() { + // a negative value indicates that we should ignore this override + final int delay = Settings.Global.getInt(mResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + -1); + + BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt( + KEY_BATTERY_CHARGED_DELAY_MS, + DEFAULT_BATTERY_CHARGED_DELAY_MS); + } + private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) { TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled; if (isEnabled && !wasEnabled) { diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 64543522893e..2c272dea073b 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -49,7 +49,7 @@ import java.util.function.ToDoubleFunction; * per thread, uid or call description. */ public class BinderCallsStats implements BinderInternal.Observer { - public static final boolean ENABLED_DEFAULT = false; + public static final boolean ENABLED_DEFAULT = true; public static final boolean DETAILED_TRACKING_DEFAULT = true; public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100; public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000; diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 0b329d70f7af..c8d30b27d4dc 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -17,8 +17,9 @@ package com.android.internal.os; import android.app.ApplicationLoaders; +import android.app.LoadedApk; +import android.content.pm.ApplicationInfo; import android.net.LocalSocket; -import android.os.Build; import android.text.TextUtils; import android.util.Log; import android.webkit.WebViewFactory; @@ -66,6 +67,34 @@ class WebViewZygoteInit { } @Override + protected boolean canPreloadApp() { + return true; + } + + @Override + protected void handlePreloadApp(ApplicationInfo appInfo) { + Log.i(TAG, "Beginning application preload for " + appInfo.packageName); + LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false); + ClassLoader loader = loadedApk.getClassLoader(); + doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo)); + + // Add the APK to the Zygote's list of allowed files for children. + Zygote.nativeAllowFileAcrossFork(appInfo.sourceDir); + if (appInfo.splitSourceDirs != null) { + for (String path : appInfo.splitSourceDirs) { + Zygote.nativeAllowFileAcrossFork(path); + } + } + if (appInfo.sharedLibraryFiles != null) { + for (String path : appInfo.sharedLibraryFiles) { + Zygote.nativeAllowFileAcrossFork(path); + } + } + + Log.i(TAG, "Application preload done"); + } + + @Override protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName, String cacheKey) { Log.i(TAG, "Beginning package preload"); @@ -76,16 +105,22 @@ class WebViewZygoteInit { ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader( packagePath, libsPath, cacheKey); - // Load the native library using WebViewLibraryLoader to share the RELRO data with other - // processes. - WebViewLibraryLoader.loadNativeLibrary(loader, libFileName); - // Add the APK to the Zygote's list of allowed files for children. String[] packageList = TextUtils.split(packagePath, File.pathSeparator); for (String packageEntry : packageList) { Zygote.nativeAllowFileAcrossFork(packageEntry); } + doPreload(loader, libFileName); + + Log.i(TAG, "Package preload done"); + } + + private void doPreload(ClassLoader loader, String libFileName) { + // Load the native library using WebViewLibraryLoader to share the RELRO data with other + // processes. + WebViewLibraryLoader.loadNativeLibrary(loader, libFileName); + // Once we have the classloader, look up the WebViewFactoryProvider implementation and // call preloadInZygote() on it to give it the opportunity to preload the native library // and perform any other initialisation work that should be shared among the children. @@ -114,8 +149,6 @@ class WebViewZygoteInit { } catch (IOException ioe) { throw new IllegalStateException("Error writing to command socket", ioe); } - - Log.i(TAG, "Package preload done"); } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index b28f4cdd5a87..069413fe6678 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -16,13 +16,33 @@ package com.android.internal.os; +import static android.system.OsConstants.O_CLOEXEC; + +import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; + +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.FactoryTest; import android.os.IVold; +import android.os.Process; +import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; +import android.util.Log; import dalvik.system.ZygoteHooks; +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStreamReader; + /** @hide */ public final class Zygote { /* @@ -94,6 +114,24 @@ public final class Zygote { /** Read-write external storage should be mounted instead of package sandbox */ public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL; + /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */ + public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8; + + /** + * If the blastula pool should be created and used to start applications. + * + * Setting this value to false will disable the creation, maintenance, and use of the blastula + * pool. When the blastula pool is disabled the application lifecycle will be identical to + * previous versions of Android. + */ + public static final boolean BLASTULA_POOL_ENABLED = false; + + /** + * File descriptor used for communication between the signal handler and the ZygoteServer poll + * loop. + * */ + protected static FileDescriptor sBlastulaPoolEventFD; + private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); /** @@ -123,6 +161,48 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end="; + /** Prefix prepended to socket names created by init */ + private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; + + /** + * The maximum value that the sBlastulaPoolMax variable may take. This value + * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. + */ + static final int BLASTULA_POOL_MAX_LIMIT = 10; + + /** + * The minimum value that the sBlastulaPoolMin variable may take. + */ + static final int BLASTULA_POOL_MIN_LIMIT = 1; + + /** + * The runtime-adjustable maximum Blastula pool size. + */ + static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT; + + /** + * The runtime-adjustable minimum Blastula pool size. + */ + static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT; + + /** + * The runtime-adjustable value used to determine when to re-fill the + * blastula pool. The pool will be re-filled when + * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold. + */ + // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax. + static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2); + + /** + * @hide for internal use only + */ + public static final int SOCKET_BUFFER_SIZE = 256; + + private static LocalServerSocket sBlastulaPoolSocket = null; + + /** a prototype instance for a future List.toArray() */ + protected static final int[][] INT_ARRAY_2D = new int[0][0]; + private Zygote() {} /** Called for some security initialization before any fork. */ @@ -165,14 +245,14 @@ public final class Zygote { public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - String packageName, String[] packagesForUid, String[] visibleVolIds) { + String packageName, String[] packagesForUID, String[] visibleVolIDs) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName, - packagesForUid, visibleVolIds); + packagesForUID, visibleVolIDs); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -187,12 +267,57 @@ public final class Zygote { private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, - String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds); + String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs); + + /** + * Specialize a Blastula instance. The current VM must have been started + * with the -Xzygote flag. + * + * @param uid The UNIX uid that the new process should setuid() to before spawning any threads + * @param gid The UNIX gid that the new process should setgid() to before spawning any threads + * @param gids null-ok; A list of UNIX gids that the new process should + * setgroups() to before spawning any threads + * @param runtimeFlags Bit flags that enable ART features + * @param rlimits null-ok An array of rlimit tuples, with the second + * dimension having a length of 3 and representing + * (resource, rlim_cur, rlim_max). These are set via the posix + * setrlimit(2) call. + * @param seInfo null-ok A string specifying SELinux information for + * the new process. + * @param niceName null-ok A string specifying the process name. + * @param startChildZygote If true, the new child process will itself be a + * new zygote process. + * @param instructionSet null-ok The instruction set to use. + * @param appDataDir null-ok The data directory of the app. + */ + public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir, String packageName, + String[] packagesForUID, String[] visibleVolIDs) { + + nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, + niceName, startChildZygote, instructionSet, appDataDir, + packageName, packagesForUID, visibleVolIDs); + + // Enable tracing as soon as possible for the child process. + Trace.setTracingEnabled(true, runtimeFlags); + + // Note that this event ends at the end of handleChildProc. + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + /* + * This is called here (instead of after the fork but before the specialize) to maintain + * consistancy with the code paths for forkAndSpecialize. + * + * TODO (chriswailes): Look into moving this to immediately after the fork. + */ + VM_HOOKS.postForkCommon(); + } private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, boolean startChildZygote, String instructionSet, String appDataDir, String packageName, - String[] packagesForUid, String[] visibleVolIds); + String[] packagesForUID, String[] visibleVolIDs); /** * Called to do any initialization before starting an application. @@ -259,20 +384,483 @@ public final class Zygote { */ protected static native void nativeUnmountStorageOnInit(); + /** + * Get socket file descriptors (opened by init) from the environment and + * store them for access from native code later. + * + * @param isPrimary True if this is the zygote process, false if it is zygote_secondary + */ + public static void getSocketFDs(boolean isPrimary) { + nativeGetSocketFDs(isPrimary); + } + protected static native void nativeGetSocketFDs(boolean isPrimary); + /** + * Initialize the blastula pool and fill it with the desired number of + * processes. + */ + protected static Runnable initBlastulaPool() { + if (BLASTULA_POOL_ENABLED) { + sBlastulaPoolEventFD = getBlastulaPoolEventFD(); + + return fillBlastulaPool(null); + } else { + return null; + } + } + + /** + * Checks to see if the current policy says that pool should be refilled, and spawns new + * blastulas if necessary. + * + * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is + * only called from contexts that are only valid if the pool is enabled. + * + * @param sessionSocketRawFDs Anonymous session sockets that are currently open + * @return In the Zygote process this function will always return null; in blastula processes + * this function will return a Runnable object representing the new application that is + * passed up from blastulaMain. + */ + protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); + + int blastulaPoolCount = getBlastulaPoolCount(); + + int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount; + + if (blastulaPoolCount < sBlastulaPoolMin + || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) { + + // Disable some VM functionality and reset some system values + // before forking. + VM_HOOKS.preFork(); + resetNicePriority(); + + while (blastulaPoolCount++ < sBlastulaPoolMax) { + Runnable caller = forkBlastula(sessionSocketRawFDs); + + if (caller != null) { + return caller; + } + } + + // Re-enable runtime services for the Zygote. Blastula services + // are re-enabled in specializeBlastula. + VM_HOOKS.postForkCommon(); + + Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); + } + + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + return null; + } + + /** + * @return Number of blastulas currently in the pool + */ + private static int getBlastulaPoolCount() { + return nativeGetBlastulaPoolCount(); + } + private static native int nativeGetBlastulaPoolCount(); + /** + * @return The event FD used for communication between the signal handler and the ZygoteServer + * poll loop + */ + private static FileDescriptor getBlastulaPoolEventFD() { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(nativeGetBlastulaPoolEventFD()); + + return fd; + } + private static native int nativeGetBlastulaPoolEventFD(); + /** + * Fork a new blastula process from the zygote + * + * @param sessionSocketRawFDs Anonymous session sockets that are currently open + * @return In the Zygote process this function will always return null; in blastula processes + * this function will return a Runnable object representing the new application that is + * passed up from blastulaMain. + */ + private static Runnable forkBlastula(int[] sessionSocketRawFDs) { + FileDescriptor[] pipeFDs = null; + + try { + pipeFDs = Os.pipe2(O_CLOEXEC); + } catch (ErrnoException errnoEx) { + throw new IllegalStateException("Unable to create blastula pipe.", errnoEx); + } + + int pid = + nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs); + + if (pid == 0) { + IoUtils.closeQuietly(pipeFDs[0]); + return blastulaMain(pipeFDs[1]); + } else { + // The read-end of the pipe will be closed by the native code. + // See removeBlastulaTableEntry(); + IoUtils.closeQuietly(pipeFDs[1]); + return null; + } + } + private static native int nativeForkBlastula(int readPipeFD, int writePipeFD, int[] sessionSocketRawFDs); + /** + * This function is used by blastulas to wait for specialization requests from the system + * server. + * + * @param writePipe The write end of the reporting pipe used to communicate with the poll loop + * of the ZygoteServer. + * @return A runnable oject representing the new application. + */ + static Runnable blastulaMain(FileDescriptor writePipe) { + final int pid = Process.myPid(); + + LocalSocket sessionSocket = null; + DataOutputStream blastulaOutputStream = null; + Credentials peerCredentials = null; + String[] argStrings = null; + + while (true) { + try { + sessionSocket = sBlastulaPoolSocket.accept(); + + BufferedReader blastulaReader = + new BufferedReader(new InputStreamReader(sessionSocket.getInputStream())); + blastulaOutputStream = + new DataOutputStream(sessionSocket.getOutputStream()); + + peerCredentials = sessionSocket.getPeerCredentials(); + + argStrings = readArgumentList(blastulaReader); + + if (argStrings != null) { + break; + } else { + Log.e("Blastula", "Truncated command received."); + IoUtils.closeQuietly(sessionSocket); + } + } catch (IOException ioEx) { + Log.e("Blastula", "Failed to read command: " + ioEx.getMessage()); + IoUtils.closeQuietly(sessionSocket); + } + } + + ZygoteArguments args = new ZygoteArguments(argStrings); + + // TODO (chriswailes): Should this only be run for debug builds? + validateBlastulaCommand(args); + + applyUidSecurityPolicy(args, peerCredentials); + applyDebuggerSystemProperty(args); + + int[][] rlimits = null; + + if (args.mRLimits != null) { + rlimits = args.mRLimits.toArray(INT_ARRAY_2D); + } + + // This must happen before the SELinux policy for this process is + // changed when specializing. + try { + // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a + // Process.ProcessStartResult object. + blastulaOutputStream.writeInt(pid); + } catch (IOException ioEx) { + Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage()); + System.exit(-1); + } finally { + IoUtils.closeQuietly(sessionSocket); + IoUtils.closeQuietly(sBlastulaPoolSocket); + } + + try { + ByteArrayOutputStream buffer = + new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES); + DataOutputStream outputStream = new DataOutputStream(buffer); + + // This is written as a long so that the blastula reporting pipe and blastula pool + // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two cases + // should both send/receive 8 bytes. + outputStream.writeLong(pid); + outputStream.flush(); + + Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); + } catch (Exception ex) { + Log.e("Blastula", + String.format("Failed to write PID (%d) to pipe (%d): %s", + pid, writePipe.getInt$(), ex.getMessage())); + System.exit(-1); + } finally { + IoUtils.closeQuietly(writePipe); + } + + specializeBlastula(args.mUid, args.mGid, args.mGids, + args.mRuntimeFlags, rlimits, args.mMountExternal, + args.mSeInfo, args.mNiceName, args.mStartChildZygote, + args.mInstructionSet, args.mAppDataDir, args.mPackageName, + args.mPackagesForUid, args.mVisibleVolIds); + + if (args.mNiceName != null) { + Process.setArgV0(args.mNiceName); + } + + // End of the postFork event. + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + return ZygoteInit.zygoteInit(args.mTargetSdkVersion, + args.mRemainingArgs, + null /* classLoader */); + } + + private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: "; + + /** + * Checks a set of zygote arguments to see if they can be handled by a blastula. Throws an + * exception if an invalid arugment is encountered. + * @param args The arguments to test + */ + static void validateBlastulaCommand(ZygoteArguments args) { + if (args.mAbiListQuery) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list"); + } else if (args.mPidQuery) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid"); + } else if (args.mPreloadDefault) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default"); + } else if (args.mPreloadPackage != null) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package"); + } else if (args.mPreloadApp != null) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-app"); + } else if (args.mStartChildZygote) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote"); + } else if (args.mApiBlacklistExemptions != null) { + throw new IllegalArgumentException( + BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions"); + } else if (args.mHiddenApiAccessLogSampleRate != -1) { + throw new IllegalArgumentException( + BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate="); + } else if (args.mInvokeWith != null) { + throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with"); + } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities)); + } + } + + /** + * @return Raw file descriptors for the read-end of blastula reporting pipes. + */ + protected static int[] getBlastulaPipeFDs() { + return nativeGetBlastulaPipeFDs(); + } + private static native int[] nativeGetBlastulaPipeFDs(); + /** + * Remove the blastula table entry for the provided process ID. + * + * @param blastulaPID Process ID of the entry to remove + * @return True if the entry was removed; false if it doesn't exist + */ + protected static boolean removeBlastulaTableEntry(int blastulaPID) { + return nativeRemoveBlastulaTableEntry(blastulaPID); + } + private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID); + /** + * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal + * operation. It may also specify any gid and setgroups() list it chooses. + * In factory test mode, it may specify any UID. + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + + if (peer.getUid() == Process.SYSTEM_UID) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; + + if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); + } + } + + // If not otherwise specified, uid and gid are inherited from peer + if (!args.mUidSpecified) { + args.mUid = peer.getUid(); + args.mUidSpecified = true; + } + if (!args.mGidSpecified) { + args.mGid = peer.getGid(); + args.mGidSpecified = true; + } + } + + /** + * Applies debugger system properties to the zygote arguments. + * + * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, + * the debugger state is specified via the "--enable-jdwp" flag + * in the spawn request. + * + * @param args non-null; zygote spawner args + */ + protected static void applyDebuggerSystemProperty(ZygoteArguments args) { + if (RoSystemProperties.DEBUGGABLE) { + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + } + } + + /** + * Applies zygote security policy. + * Based on the credentials of the process issuing a zygote command: + * <ol> + * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a + * wrapper command. + * <li> Any other uid may not specify any invoke-with argument. + * </ul> + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + int peerUid = peer.getUid(); + + if (args.mInvokeWith != null && peerUid != 0 + && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an" + + "explicit invoke-with wrapper command only for debuggable" + + "applications."); + } + } + + /** + * Applies invoke-with system properties to the zygote arguments. + * + * @param args non-null; zygote args + */ + protected static void applyInvokeWithSystemProperty(ZygoteArguments args) { + if (args.mInvokeWith == null && args.mNiceName != null) { + String property = "wrap." + args.mNiceName; + args.mInvokeWith = SystemProperties.get(property); + if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) { + args.mInvokeWith = null; + } + } + } + + /** + * Reads an argument list from the provided socket + * @return Argument list or null if EOF is reached + * @throws IOException passed straight through + */ + static String[] readArgumentList(BufferedReader socketReader) throws IOException { + + /** + * See android.os.Process.zygoteSendArgsAndGetPid() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure. + */ + + int argc; + + try { + String argc_string = socketReader.readLine(); + + if (argc_string == null) { + // EOF reached. + return null; + } + argc = Integer.parseInt(argc_string); + + } catch (NumberFormatException ex) { + Log.e("Zygote", "Invalid Zygote wire format: non-int at argc"); + throw new IOException("Invalid wire format"); + } + + // See bug 1092107: large argc can be used for a DOS attack + if (argc > MAX_ZYGOTE_ARGC) { + throw new IOException("Max arg count exceeded"); + } + + String[] args = new String[argc]; + for (int arg_index = 0; arg_index < argc; arg_index++) { + args[arg_index] = socketReader.readLine(); + if (args[arg_index] == null) { + // We got an unexpected EOF. + throw new IOException("Truncated request"); + } + } + + return args; + } + + /** + * Creates a managed object representing the Blastula pool socket that has + * already been initialized and bound by init. + * + * TODO (chriswailes): Move the name selection logic into this function. + * + * @throws RuntimeException when open fails + */ + static void createBlastulaSocket(String socketName) { + if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) { + sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName); + } + } + + /** + * Creates a managed LocalServerSocket object using a file descriptor + * created by an init.rc script. The init scripts that specify the + * sockets name can be found in system/core/rootdir. The socket is bound + * to the file system in the /dev/sockets/ directory, and the file + * descriptor is shared via the ANDROID_SOCKET_<socketName> environment + * variable. + */ + static LocalServerSocket createManagedSocketFromInitSocket(String socketName) { + int fileDesc; + final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; + + try { + String env = System.getenv(fullSocketName); + fileDesc = Integer.parseInt(env); + } catch (RuntimeException ex) { + throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex); + } + + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(fileDesc); + return new LocalServerSocket(fd); + } catch (IOException ex) { + throw new RuntimeException( + "Error building socket from file descriptor: " + fileDesc, ex); + } + } private static void callPostForkSystemServerHooks() { // SystemServer specific post fork hooks run before child post fork hooks. diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java new file mode 100644 index 000000000000..24a08ca5b1e0 --- /dev/null +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2007 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.os; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Handles argument parsing for args related to the zygote spawner. + * + * Current recognized args: + * <ul> + * <li> --setuid=<i>uid of child process, defaults to 0</i> + * <li> --setgid=<i>gid of child process, defaults to 0</i> + * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> + * <li> --capabilities=<i>a pair of comma-separated integer strings + * indicating Linux capabilities(2) set for child. The first string + * represents the <code>permitted</code> set, and the second the + * <code>effective</code> set. Precede each with 0 or + * 0x for octal or hexidecimal value. If unspecified, both default to 0. + * This parameter is only applied if the uid of the new process will + * be non-0. </i> + * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. + * <code>r</code> is the resource, <code>c</code> and <code>m</code> + * are the settings for current and max value.</i> + * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. + * <li> --nice-name=<i>nice name to appear in ps</i> + * <li> --package-name=<i>package name this process belongs to</i> + * <li> --runtime-args indicates that the remaining arg list should + * be handed off to com.android.internal.os.RuntimeInit, rather than + * processed directly. + * Android runtime startup (eg, Binder initialization) is also eschewed. + * <li> [--] <args for RuntimeInit > + * </ul> + */ +class ZygoteArguments { + + /** + * from --setuid + */ + int mUid = 0; + boolean mUidSpecified; + + /** + * from --setgid + */ + int mGid = 0; + boolean mGidSpecified; + + /** + * from --setgroups + */ + int[] mGids; + + /** + * From --runtime-flags. + */ + int mRuntimeFlags; + + /** + * From --mount-external + */ + int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE; + + /** + * from --target-sdk-version. + */ + int mTargetSdkVersion; + boolean mTargetSdkVersionSpecified; + + /** + * from --nice-name + */ + String mNiceName; + + /** + * from --capabilities + */ + boolean mCapabilitiesSpecified; + long mPermittedCapabilities; + long mEffectiveCapabilities; + + /** + * from --seinfo + */ + boolean mSeInfoSpecified; + String mSeInfo; + + /** + * from all --rlimit=r,c,m + */ + ArrayList<int[]> mRLimits; + + /** + * from --invoke-with + */ + String mInvokeWith; + + /** from --package-name */ + String mPackageName; + + /** from --packages-for-uid */ + String[] mPackagesForUid; + + /** from --visible-vols */ + String[] mVisibleVolIds; + + /** + * Any args after and including the first non-option arg (or after a '--') + */ + String[] mRemainingArgs; + + /** + * Whether the current arguments constitute an ABI list query. + */ + boolean mAbiListQuery; + + /** + * The instruction set to use, or null when not important. + */ + String mInstructionSet; + + /** + * The app data directory. May be null, e.g., for the system server. Note that this might not be + * reliable in the case of process-sharing apps. + */ + String mAppDataDir; + + /** + * The APK path of the package to preload, when using --preload-package. + */ + String mPreloadPackage; + + /** + * A Base64 string representing a serialize ApplicationInfo Parcel, + when using --preload-app. + */ + String mPreloadApp; + + /** + * The native library path of the package to preload, when using --preload-package. + */ + String mPreloadPackageLibs; + + /** + * The filename of the native library to preload, when using --preload-package. + */ + String mPreloadPackageLibFileName; + + /** + * The cache key under which to enter the preloaded package into the classloader cache, when + * using --preload-package. + */ + String mPreloadPackageCacheKey; + + /** + * Whether this is a request to start preloading the default resources and classes. This + * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started + * with --enable-lazy-preload). + */ + boolean mPreloadDefault; + + /** + * Whether this is a request to start a zygote process as a child of this zygote. Set with + * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG + * flag to indicate the abstract socket name that should be used for communication. + */ + boolean mStartChildZygote; + + /** + * Whether the current arguments constitute a request for the zygote's PID. + */ + boolean mPidQuery; + + /** + * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or + * when they change, via --set-api-blacklist-exemptions. + */ + String[] mApiBlacklistExemptions; + + /** + * Sampling rate for logging hidden API accesses to the event log. This is sent to the + * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. + */ + int mHiddenApiAccessLogSampleRate = -1; + + /** + * Constructs instance and parses args + * + * @param args zygote command-line args + */ + ZygoteArguments(String[] args) throws IllegalArgumentException { + parseArgs(args); + } + + /** + * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and + * "--setgid=") and creates an array containing the remaining args. + * + * Per security review bug #1112214, duplicate args are disallowed in critical cases to make + * injection harder. + */ + private void parseArgs(String[] args) + throws IllegalArgumentException { + int curArg = 0; + + boolean seenRuntimeArgs = false; + + boolean expectRuntimeArgs = true; + for ( /* curArg */ ; curArg < args.length; curArg++) { + String arg = args[curArg]; + + if (arg.equals("--")) { + curArg++; + break; + } else if (arg.startsWith("--setuid=")) { + if (mUidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mUidSpecified = true; + mUid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--setgid=")) { + if (mGidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mGidSpecified = true; + mGid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--target-sdk-version=")) { + if (mTargetSdkVersionSpecified) { + throw new IllegalArgumentException( + "Duplicate target-sdk-version specified"); + } + mTargetSdkVersionSpecified = true; + mTargetSdkVersion = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.equals("--runtime-args")) { + seenRuntimeArgs = true; + } else if (arg.startsWith("--runtime-flags=")) { + mRuntimeFlags = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--seinfo=")) { + if (mSeInfoSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mSeInfoSpecified = true; + mSeInfo = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--capabilities=")) { + if (mCapabilitiesSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mCapabilitiesSpecified = true; + String capString = arg.substring(arg.indexOf('=') + 1); + + String[] capStrings = capString.split(",", 2); + + if (capStrings.length == 1) { + mEffectiveCapabilities = Long.decode(capStrings[0]); + mPermittedCapabilities = mEffectiveCapabilities; + } else { + mPermittedCapabilities = Long.decode(capStrings[0]); + mEffectiveCapabilities = Long.decode(capStrings[1]); + } + } else if (arg.startsWith("--rlimit=")) { + // Duplicate --rlimit arguments are specifically allowed. + String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(","); + + if (limitStrings.length != 3) { + throw new IllegalArgumentException( + "--rlimit= should have 3 comma-delimited ints"); + } + int[] rlimitTuple = new int[limitStrings.length]; + + for (int i = 0; i < limitStrings.length; i++) { + rlimitTuple[i] = Integer.parseInt(limitStrings[i]); + } + + if (mRLimits == null) { + mRLimits = new ArrayList(); + } + + mRLimits.add(rlimitTuple); + } else if (arg.startsWith("--setgroups=")) { + if (mGids != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + + String[] params = arg.substring(arg.indexOf('=') + 1).split(","); + + mGids = new int[params.length]; + + for (int i = params.length - 1; i >= 0; i--) { + mGids[i] = Integer.parseInt(params[i]); + } + } else if (arg.equals("--invoke-with")) { + if (mInvokeWith != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + try { + mInvokeWith = args[++curArg]; + } catch (IndexOutOfBoundsException ex) { + throw new IllegalArgumentException( + "--invoke-with requires argument"); + } + } else if (arg.startsWith("--nice-name=")) { + if (mNiceName != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mNiceName = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--mount-external-default")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; + } else if (arg.equals("--mount-external-read")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_READ; + } else if (arg.equals("--mount-external-write")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE; + } else if (arg.equals("--mount-external-full")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_FULL; + } else if (arg.equals("--mount-external-installer")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; + } else if (arg.equals("--mount-external-legacy")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY; + } else if (arg.equals("--query-abi-list")) { + mAbiListQuery = true; + } else if (arg.equals("--get-pid")) { + mPidQuery = true; + } else if (arg.startsWith("--instruction-set=")) { + mInstructionSet = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--app-data-dir=")) { + mAppDataDir = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--preload-app")) { + mPreloadApp = args[++curArg]; + } else if (arg.equals("--preload-package")) { + mPreloadPackage = args[++curArg]; + mPreloadPackageLibs = args[++curArg]; + mPreloadPackageLibFileName = args[++curArg]; + mPreloadPackageCacheKey = args[++curArg]; + } else if (arg.equals("--preload-default")) { + mPreloadDefault = true; + expectRuntimeArgs = false; + } else if (arg.equals("--start-child-zygote")) { + mStartChildZygote = true; + } else if (arg.equals("--set-api-blacklist-exemptions")) { + // consume all remaining args; this is a stand-alone command, never included + // with the regular fork command. + mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); + curArg = args.length; + expectRuntimeArgs = false; + } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { + String rateStr = arg.substring(arg.indexOf('=') + 1); + try { + mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid log sampling rate: " + rateStr, nfe); + } + expectRuntimeArgs = false; + } else if (arg.startsWith("--package-name=")) { + if (mPackageName != null) { + throw new IllegalArgumentException("Duplicate arg specified"); + } + mPackageName = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--packages-for-uid=")) { + mPackagesForUid = arg.substring(arg.indexOf('=') + 1).split(","); + } else if (arg.startsWith("--visible-vols=")) { + mVisibleVolIds = arg.substring(arg.indexOf('=') + 1).split(","); + } else { + break; + } + } + + if (mAbiListQuery || mPidQuery) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); + } + } else if (mPreloadPackage != null) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException( + "Unexpected arguments after --preload-package."); + } + } else if (mPreloadApp != null) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException( + "Unexpected arguments after --preload-app."); + } + } else if (expectRuntimeArgs) { + if (!seenRuntimeArgs) { + throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); + } + + mRemainingArgs = new String[args.length - curArg]; + System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); + } + + if (mStartChildZygote) { + boolean seenChildSocketArg = false; + for (String arg : mRemainingArgs) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { + seenChildSocketArg = true; + break; + } + } + if (!seenChildSocketArg) { + throw new IllegalArgumentException("--start-child-zygote specified " + + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); + } + } + } +} diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index ced798cb6445..ffbe8ebccb9e 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -24,16 +24,13 @@ import static android.system.OsConstants.STDIN_FILENO; import static android.system.OsConstants.STDOUT_FILENO; import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; -import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalSocket; -import android.os.FactoryTest; import android.os.Parcel; import android.os.Process; -import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; @@ -52,8 +49,6 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Base64; /** @@ -62,9 +57,6 @@ import java.util.Base64; class ZygoteConnection { private static final String TAG = "Zygote"; - /** a prototype instance for a future List.toArray() */ - private static final int[][] intArray2d = new int[0][0]; - /** * The command socket. * @@ -113,7 +105,7 @@ class ZygoteConnection { * * @return null-ok; file descriptor */ - FileDescriptor getFileDesciptor() { + FileDescriptor getFileDescriptor() { return mSocket.getFileDescriptor(); } @@ -127,11 +119,13 @@ class ZygoteConnection { */ Runnable processOneCommand(ZygoteServer zygoteServer) { String args[]; - Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; FileDescriptor[] descriptors; try { - args = readArgumentList(); + args = Zygote.readArgumentList(mSocketReader); + + // TODO (chriswailes): Remove this and add an assert. descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { throw new IllegalStateException("IOException on command socket", ex); @@ -148,26 +142,26 @@ class ZygoteConnection { FileDescriptor childPipeFd = null; FileDescriptor serverPipeFd = null; - parsedArgs = new Arguments(args); + parsedArgs = new ZygoteArguments(args); - if (parsedArgs.abiListQuery) { + if (parsedArgs.mAbiListQuery) { handleAbiListQuery(); return null; } - if (parsedArgs.pidQuery) { + if (parsedArgs.mPidQuery) { handlePidQuery(); return null; } - if (parsedArgs.preloadDefault) { + if (parsedArgs.mPreloadDefault) { handlePreload(); return null; } - if (parsedArgs.preloadPackage != null) { - handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs, - parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey); + if (parsedArgs.mPreloadPackage != null) { + handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, + parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); return null; } @@ -186,37 +180,37 @@ class ZygoteConnection { return null; } - if (parsedArgs.apiBlacklistExemptions != null) { - handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions); + if (parsedArgs.mApiBlacklistExemptions != null) { + handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions); return null; } - if (parsedArgs.hiddenApiAccessLogSampleRate != -1) { - handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate); + if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) { + handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate); return null; } - if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { - throw new ZygoteSecurityException("Client may not specify capabilities: " + - "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) + - ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities)); + if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); } - applyUidSecurityPolicy(parsedArgs, peer); - applyInvokeWithSecurityPolicy(parsedArgs, peer); + Zygote.applyUidSecurityPolicy(parsedArgs, peer); + Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); - applyDebuggerSystemProperty(parsedArgs); - applyInvokeWithSystemProperty(parsedArgs); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); int[][] rlimits = null; - if (parsedArgs.rlimits != null) { - rlimits = parsedArgs.rlimits.toArray(intArray2d); + if (parsedArgs.mRLimits != null) { + rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); } int[] fdsToIgnore = null; - if (parsedArgs.invokeWith != null) { + if (parsedArgs.mInvokeWith != null) { try { FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); childPipeFd = pipeFds[1]; @@ -248,7 +242,7 @@ class ZygoteConnection { fdsToClose[0] = fd.getInt$(); } - fd = zygoteServer.getServerSocketFileDescriptor(); + fd = zygoteServer.getZygoteSocketFileDescriptor(); if (fd != null) { fdsToClose[1] = fd.getInt$(); @@ -256,11 +250,11 @@ class ZygoteConnection { fd = null; - pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, - parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, - parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, - parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName, - parsedArgs.packagesForUid, parsedArgs.visibleVolIds); + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, + parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, + parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mPackageName, + parsedArgs.mPackagesForUid, parsedArgs.mVisibleVolIds); try { if (pid == 0) { @@ -272,7 +266,7 @@ class ZygoteConnection { serverPipeFd = null; return handleChildProc(parsedArgs, descriptors, childPipeFd, - parsedArgs.startChildZygote); + parsedArgs.mStartChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. @@ -387,541 +381,6 @@ class ZygoteConnection { } /** - * Handles argument parsing for args related to the zygote spawner. - * - * Current recognized args: - * <ul> - * <li> --setuid=<i>uid of child process, defaults to 0</i> - * <li> --setgid=<i>gid of child process, defaults to 0</i> - * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> - * <li> --capabilities=<i>a pair of comma-separated integer strings - * indicating Linux capabilities(2) set for child. The first string - * represents the <code>permitted</code> set, and the second the - * <code>effective</code> set. Precede each with 0 or - * 0x for octal or hexidecimal value. If unspecified, both default to 0. - * This parameter is only applied if the uid of the new process will - * be non-0. </i> - * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. - * <code>r</code> is the resource, <code>c</code> and <code>m</code> - * are the settings for current and max value.</i> - * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. - * <li> --nice-name=<i>nice name to appear in ps</i> - * <li> --package-name=<i>package name this process belongs to</i> - * <li> --runtime-args indicates that the remaining arg list should - * be handed off to com.android.internal.os.RuntimeInit, rather than - * processed directly. - * Android runtime startup (eg, Binder initialization) is also eschewed. - * <li> [--] <args for RuntimeInit > - * </ul> - */ - static class Arguments { - /** from --setuid */ - int uid = 0; - boolean uidSpecified; - - /** from --setgid */ - int gid = 0; - boolean gidSpecified; - - /** from --setgroups */ - int[] gids; - - /** - * From --runtime-flags. - */ - int runtimeFlags; - - /** From --mount-external */ - int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; - - /** from --target-sdk-version. */ - int targetSdkVersion; - boolean targetSdkVersionSpecified; - - /** from --nice-name */ - String niceName; - - /** from --capabilities */ - boolean capabilitiesSpecified; - long permittedCapabilities; - long effectiveCapabilities; - - /** from --seinfo */ - boolean seInfoSpecified; - String seInfo; - - /** from all --rlimit=r,c,m */ - ArrayList<int[]> rlimits; - - /** from --invoke-with */ - String invokeWith; - - /** from --package-name */ - String packageName; - - /** from --packages-for-uid */ - String[] packagesForUid; - - /** from --visible-vols */ - String[] visibleVolIds; - - /** - * Any args after and including the first non-option arg - * (or after a '--') - */ - String remainingArgs[]; - - /** - * Whether the current arguments constitute an ABI list query. - */ - boolean abiListQuery; - - /** - * The instruction set to use, or null when not important. - */ - String instructionSet; - - /** - * The app data directory. May be null, e.g., for the system server. Note that this might - * not be reliable in the case of process-sharing apps. - */ - String appDataDir; - - /** - * The APK path of the package to preload, when using --preload-package. - */ - String preloadPackage; - - /** - * A Base64 string representing a serialize ApplicationInfo Parcel, - when using --preload-app. - */ - String mPreloadApp; - - /** - * The native library path of the package to preload, when using --preload-package. - */ - String preloadPackageLibs; - - /** - * The filename of the native library to preload, when using --preload-package. - */ - String preloadPackageLibFileName; - - /** - * The cache key under which to enter the preloaded package into the classloader cache, - * when using --preload-package. - */ - String preloadPackageCacheKey; - - /** - * Whether this is a request to start preloading the default resources and classes. - * This argument only makes sense when the zygote is in lazy preload mode (i.e, when - * it's started with --enable-lazy-preload). - */ - boolean preloadDefault; - - /** - * Whether this is a request to start a zygote process as a child of this zygote. - * Set with --start-child-zygote. The remaining arguments must include the - * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that - * should be used for communication. - */ - boolean startChildZygote; - - /** - * Whether the current arguments constitute a request for the zygote's PID. - */ - boolean pidQuery; - - /** - * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, - * or when they change, via --set-api-blacklist-exemptions. - */ - String[] apiBlacklistExemptions; - - /** - * Sampling rate for logging hidden API accesses to the event log. This is sent to the - * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. - */ - int hiddenApiAccessLogSampleRate = -1; - - /** - * Constructs instance and parses args - * @param args zygote command-line args - * @throws IllegalArgumentException - */ - Arguments(String args[]) throws IllegalArgumentException { - parseArgs(args); - } - - /** - * Parses the commandline arguments intended for the Zygote spawner - * (such as "--setuid=" and "--setgid=") and creates an array - * containing the remaining args. - * - * Per security review bug #1112214, duplicate args are disallowed in - * critical cases to make injection harder. - */ - private void parseArgs(String args[]) - throws IllegalArgumentException { - int curArg = 0; - - boolean seenRuntimeArgs = false; - - boolean expectRuntimeArgs = true; - for ( /* curArg */ ; curArg < args.length; curArg++) { - String arg = args[curArg]; - - if (arg.equals("--")) { - curArg++; - break; - } else if (arg.startsWith("--setuid=")) { - if (uidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - uidSpecified = true; - uid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--setgid=")) { - if (gidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - gidSpecified = true; - gid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--target-sdk-version=")) { - if (targetSdkVersionSpecified) { - throw new IllegalArgumentException( - "Duplicate target-sdk-version specified"); - } - targetSdkVersionSpecified = true; - targetSdkVersion = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.equals("--runtime-args")) { - seenRuntimeArgs = true; - } else if (arg.startsWith("--runtime-flags=")) { - runtimeFlags = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--seinfo=")) { - if (seInfoSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - seInfoSpecified = true; - seInfo = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--capabilities=")) { - if (capabilitiesSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - capabilitiesSpecified = true; - String capString = arg.substring(arg.indexOf('=')+1); - - String[] capStrings = capString.split(",", 2); - - if (capStrings.length == 1) { - effectiveCapabilities = Long.decode(capStrings[0]); - permittedCapabilities = effectiveCapabilities; - } else { - permittedCapabilities = Long.decode(capStrings[0]); - effectiveCapabilities = Long.decode(capStrings[1]); - } - } else if (arg.startsWith("--rlimit=")) { - // Duplicate --rlimit arguments are specifically allowed. - String[] limitStrings - = arg.substring(arg.indexOf('=')+1).split(","); - - if (limitStrings.length != 3) { - throw new IllegalArgumentException( - "--rlimit= should have 3 comma-delimited ints"); - } - int[] rlimitTuple = new int[limitStrings.length]; - - for(int i=0; i < limitStrings.length; i++) { - rlimitTuple[i] = Integer.parseInt(limitStrings[i]); - } - - if (rlimits == null) { - rlimits = new ArrayList(); - } - - rlimits.add(rlimitTuple); - } else if (arg.startsWith("--setgroups=")) { - if (gids != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - - String[] params - = arg.substring(arg.indexOf('=') + 1).split(","); - - gids = new int[params.length]; - - for (int i = params.length - 1; i >= 0 ; i--) { - gids[i] = Integer.parseInt(params[i]); - } - } else if (arg.equals("--invoke-with")) { - if (invokeWith != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - try { - invokeWith = args[++curArg]; - } catch (IndexOutOfBoundsException ex) { - throw new IllegalArgumentException( - "--invoke-with requires argument"); - } - } else if (arg.startsWith("--nice-name=")) { - if (niceName != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - niceName = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--mount-external-default")) { - mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; - } else if (arg.equals("--mount-external-read")) { - mountExternal = Zygote.MOUNT_EXTERNAL_READ; - } else if (arg.equals("--mount-external-write")) { - mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; - } else if (arg.equals("--mount-external-full")) { - mountExternal = Zygote.MOUNT_EXTERNAL_FULL; - } else if (arg.equals("--mount-external-installer")) { - mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; - } else if (arg.equals("--mount-external-legacy")) { - mountExternal = Zygote.MOUNT_EXTERNAL_LEGACY; - } else if (arg.equals("--query-abi-list")) { - abiListQuery = true; - } else if (arg.equals("--get-pid")) { - pidQuery = true; - } else if (arg.startsWith("--instruction-set=")) { - instructionSet = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--app-data-dir=")) { - appDataDir = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--preload-app")) { - mPreloadApp = args[++curArg]; - } else if (arg.equals("--preload-package")) { - preloadPackage = args[++curArg]; - preloadPackageLibs = args[++curArg]; - preloadPackageLibFileName = args[++curArg]; - preloadPackageCacheKey = args[++curArg]; - } else if (arg.equals("--preload-default")) { - preloadDefault = true; - expectRuntimeArgs = false; - } else if (arg.equals("--start-child-zygote")) { - startChildZygote = true; - } else if (arg.equals("--set-api-blacklist-exemptions")) { - // consume all remaining args; this is a stand-alone command, never included - // with the regular fork command. - apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); - curArg = args.length; - expectRuntimeArgs = false; - } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { - String rateStr = arg.substring(arg.indexOf('=') + 1); - try { - hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException( - "Invalid log sampling rate: " + rateStr, nfe); - } - expectRuntimeArgs = false; - } else if (arg.startsWith("--package-name=")) { - if (packageName != null) { - throw new IllegalArgumentException("Duplicate arg specified"); - } - packageName = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--packages-for-uid=")) { - packagesForUid = arg.substring(arg.indexOf('=') + 1).split(","); - } else if (arg.startsWith("--visible-vols=")) { - visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(","); - } else { - break; - } - } - - if (abiListQuery || pidQuery) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); - } - } else if (preloadPackage != null) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException( - "Unexpected arguments after --preload-package."); - } - } else if (mPreloadApp != null) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException( - "Unexpected arguments after --preload-app."); - } - } else if (expectRuntimeArgs) { - if (!seenRuntimeArgs) { - throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); - } - - remainingArgs = new String[args.length - curArg]; - System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length); - } - - if (startChildZygote) { - boolean seenChildSocketArg = false; - for (String arg : remainingArgs) { - if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { - seenChildSocketArg = true; - break; - } - } - if (!seenChildSocketArg) { - throw new IllegalArgumentException("--start-child-zygote specified " + - "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); - } - } - } - } - - /** - * Reads an argument list from the command socket/ - * @return Argument list or null if EOF is reached - * @throws IOException passed straight through - */ - private String[] readArgumentList() - throws IOException { - - /** - * See android.os.Process.zygoteSendArgsAndGetPid() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * the child or -1 on failure. - */ - - int argc; - - try { - String s = mSocketReader.readLine(); - - if (s == null) { - // EOF reached. - return null; - } - argc = Integer.parseInt(s); - } catch (NumberFormatException ex) { - Log.e(TAG, "invalid Zygote wire format: non-int at argc"); - throw new IOException("invalid wire format"); - } - - // See bug 1092107: large argc can be used for a DOS attack - if (argc > MAX_ZYGOTE_ARGC) { - throw new IOException("max arg count exceeded"); - } - - String[] result = new String[argc]; - for (int i = 0; i < argc; i++) { - result[i] = mSocketReader.readLine(); - if (result[i] == null) { - // We got an unexpected EOF. - throw new IOException("truncated request"); - } - } - - return result; - } - - /** - * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal - * operation. It may also specify any gid and setgroups() list it chooses. - * In factory test mode, it may specify any UID. - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyUidSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - - if (peer.getUid() == Process.SYSTEM_UID) { - /* In normal operation, SYSTEM_UID can only specify a restricted - * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. - */ - boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; - - if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) { - throw new ZygoteSecurityException( - "System UID may not launch process with UID < " - + Process.SYSTEM_UID); - } - } - - // If not otherwise specified, uid and gid are inherited from peer - if (!args.uidSpecified) { - args.uid = peer.getUid(); - args.uidSpecified = true; - } - if (!args.gidSpecified) { - args.gid = peer.getGid(); - args.gidSpecified = true; - } - } - - /** - * Applies debugger system properties to the zygote arguments. - * - * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, - * the debugger state is specified via the "--enable-jdwp" flag - * in the spawn request. - * - * @param args non-null; zygote spawner args - */ - public static void applyDebuggerSystemProperty(Arguments args) { - if (RoSystemProperties.DEBUGGABLE) { - args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; - } - } - - /** - * Applies zygote security policy. - * Based on the credentials of the process issuing a zygote command: - * <ol> - * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a - * wrapper command. - * <li> Any other uid may not specify any invoke-with argument. - * </ul> - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - int peerUid = peer.getUid(); - - if (args.invokeWith != null && peerUid != 0 && - (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { - throw new ZygoteSecurityException("Peer is permitted to specify an" - + "explicit invoke-with wrapper command only for debuggable" - + "applications."); - } - } - - /** - * Applies invoke-with system properties to the zygote arguments. - * - * @param args non-null; zygote args - */ - public static void applyInvokeWithSystemProperty(Arguments args) { - if (args.invokeWith == null && args.niceName != null) { - String property = "wrap." + args.niceName; - args.invokeWith = SystemProperties.get(property); - if (args.invokeWith != null && args.invokeWith.length() == 0) { - args.invokeWith = null; - } - } - } - - /** * Handles post-fork setup of child proc, closing sockets as appropriate, * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller * if successful or returning if failed. @@ -931,7 +390,7 @@ class ZygoteConnection { * @param pipeFd null-ok; pipe for communication back to Zygote. * @param isZygote whether this new child process is itself a new Zygote. */ - private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, + private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, boolean isZygote) { /** * By the time we get here, the native code has closed the two actual Zygote @@ -954,27 +413,27 @@ class ZygoteConnection { } } - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } // End of the postFork event. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - if (parsedArgs.invokeWith != null) { - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + if (parsedArgs.mInvokeWith != null) { + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), - pipeFd, parsedArgs.remainingArgs); + pipeFd, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, - null /* classLoader */); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } else { - return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion, - parsedArgs.remainingArgs, null /* classLoader */); + return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c2c6ae6712ab..e3e55ed28c6f 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -21,7 +21,6 @@ import static android.system.OsConstants.S_IRWXO; import android.content.res.Resources; import android.content.res.TypedArray; -import android.opengl.EGL14; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -71,16 +70,16 @@ import java.security.Security; /** * Startup class for the zygote process. * - * Pre-initializes some classes, and then waits for commands on a UNIX domain - * socket. Based on these commands, forks off child processes that inherit - * the initial state of the VM. + * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these + * commands, forks off child processes that inherit the initial state of the VM. * - * Please see {@link ZygoteConnection.Arguments} for documentation on the - * client protocol. + * Please see {@link ZygoteArguments} for documentation on the client protocol. * * @hide */ public class ZygoteInit { + + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate private static final String TAG = "Zygote"; private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; @@ -89,11 +88,15 @@ public class ZygoteInit { private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030; - /** when preloading, GC after allocating this many bytes */ + /** + * when preloading, GC after allocating this many bytes + */ private static final int PRELOAD_GC_THRESHOLD = 50000; private static final String ABI_LIST_ARG = "--abi-list="; + // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a + // --blastula-socket-name parameter. private static final String SOCKET_NAME_ARG = "--socket-name="; /** @@ -106,7 +109,9 @@ public class ZygoteInit { */ private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes"; - /** Controls whether we should preload resources during zygote init. */ + /** + * Controls whether we should preload resources during zygote init. + */ public static final boolean PRELOAD_RESOURCES = true; private static final int UNPRIVILEGED_UID = 9999; @@ -173,6 +178,7 @@ public class ZygoteInit { } native private static void nativePreloadAppProcessHALs(); + native private static void nativePreloadOpenGL(); private static void preloadOpenGL() { @@ -191,8 +197,8 @@ public class ZygoteInit { /** * Register AndroidKeyStoreProvider and warm up the providers that are already registered. * - * By doing it here we avoid that each app does it when requesting a service from the - * provider for the first time. + * By doing it here we avoid that each app does it when requesting a service from the provider + * for the first time. */ private static void warmUpJcaProviders() { long startTime = SystemClock.uptimeMillis(); @@ -218,11 +224,10 @@ public class ZygoteInit { } /** - * Performs Zygote process initialization. Loads and initializes - * commonly used classes. + * Performs Zygote process initialization. Loads and initializes commonly used classes. * - * Most classes only cause a few hundred bytes to be allocated, but - * a few will allocate a dozen Kbytes (in one case, 500+K). + * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen + * Kbytes (in one case, 500+K). */ private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -263,8 +268,8 @@ public class ZygoteInit { runtime.setTargetHeapUtilization(0.8f); try { - BufferedReader br - = new BufferedReader(new InputStreamReader(is), 256); + BufferedReader br = + new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE); int count = 0; String line; @@ -305,7 +310,7 @@ public class ZygoteInit { } Log.i(TAG, "...preloaded " + count + " classes in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { @@ -331,11 +336,10 @@ public class ZygoteInit { } /** - * Load in commonly used resources, so they can be shared across - * processes. + * Load in commonly used resources, so they can be shared across processes. * - * These tend to be a few Kbytes, but are frequently in the 20-40K - * range, and occasionally even larger. + * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even + * larger. */ private static void preloadResources() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -352,7 +356,7 @@ public class ZygoteInit { int N = preloadDrawables(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); startTime = SystemClock.uptimeMillis(); ar = mResources.obtainTypedArray( @@ -360,7 +364,7 @@ public class ZygoteInit { N = preloadColorStateLists(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); if (mResources.getBoolean( com.android.internal.R.bool.config_freeformWindowManagement)) { @@ -381,7 +385,7 @@ public class ZygoteInit { private static int preloadColorStateLists(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -390,8 +394,8 @@ public class ZygoteInit { if (mResources.getColorStateList(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded color resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -401,7 +405,7 @@ public class ZygoteInit { private static int preloadDrawables(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -410,8 +414,8 @@ public class ZygoteInit { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded drawable resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -419,9 +423,8 @@ public class ZygoteInit { } /** - * Runs several special GCs to try to clean up a few generations of - * softly- and final-reachable objects, along with any other garbage. - * This is only useful just before a fork(). + * Runs several special GCs to try to clean up a few generations of softly- and final-reachable + * objects, along with any other garbage. This is only useful just before a fork(). */ private static void gcAndFinalize() { ZygoteHooks.gcAndFinalize(); @@ -430,12 +433,12 @@ public class ZygoteInit { /** * Finish remaining work for the newly forked system server process. */ - private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) { + private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { // set umask to 0077 so new files and directories will default to owner-only permissions. Os.umask(S_IRWXG | S_IRWXO); - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); @@ -454,8 +457,8 @@ public class ZygoteInit { } } - if (parsedArgs.invokeWith != null) { - String[] args = parsedArgs.remainingArgs; + if (parsedArgs.mInvokeWith != null) { + String[] args = parsedArgs.mRemainingArgs; // If we have a non-null system server class path, we'll have to duplicate the // existing arguments and append the classpath to it. ART will handle the classpath // correctly when we exec a new process. @@ -467,15 +470,15 @@ public class ZygoteInit { args = amendedArgs; } - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), null, args); throw new IllegalStateException("Unexpected return from WrapperInit.execApplication"); } else { ClassLoader cl = null; if (systemServerClasspath != null) { - cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); + cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion); Thread.currentThread().setContextClassLoader(cl); } @@ -483,16 +486,17 @@ public class ZygoteInit { /* * Pass the remaining arguments to SystemServer. */ - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, cl); } /* should never reach here */ } /** - * Note that preparing the profiles for system server does not require special - * selinux permissions. From the installer perspective the system server is a regular package - * which can capture profile information. + * Note that preparing the profiles for system server does not require special selinux + * permissions. From the installer perspective the system server is a regular package which can + * capture profile information. */ private static void prepareSystemServerProfile(String systemServerClasspath) throws RemoteException { @@ -544,8 +548,8 @@ public class ZygoteInit { } /** - * Performs dex-opt on the elements of {@code classPath}, if needed. We - * choose the instruction set of the current runtime. + * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction + * set of the current runtime. */ private static void performSystemServerDexOpt(String classPath) { final String[] classPathElements = classPath.split(":"); @@ -563,8 +567,9 @@ public class ZygoteInit { int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( - classPathElement, instructionSet, systemServerFilter, - null /* classLoaderContext */, false /* newProfile */, false /* downgrade */); + classPathElement, instructionSet, systemServerFilter, + null /* classLoaderContext */, false /* newProfile */, + false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -607,8 +612,8 @@ public class ZygoteInit { } /** - * Encodes the system server class loader context in a format that is accepted by dexopt. - * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. + * Encodes the system server class loader context in a format that is accepted by dexopt. This + * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. * * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no * dependency here on the server so we hard code the logic again. @@ -619,10 +624,11 @@ public class ZygoteInit { /** * Encodes the class path in a format accepted by dexopt. - * @param classPath the old class path (may be empty). - * @param newElement the new class path elements - * @return the class path encoding resulted from appending {@code newElement} to - * {@code classPath}. + * + * @param classPath The old class path (may be empty). + * @param newElement The new class path elements + * @return The class path encoding resulted from appending {@code newElement} to {@code + * classPath}. */ private static String encodeSystemServerClassPath(String classPath, String newElement) { return (classPath == null || classPath.isEmpty()) @@ -633,25 +639,25 @@ public class ZygoteInit { /** * Prepare the arguments and forks for the system server process. * - * Returns an {@code Runnable} that provides an entrypoint into system_server code in the - * child process, and {@code null} in the parent. + * @return A {@code Runnable} that provides an entrypoint into system_server code in the child + * process; {@code null} in the parent. */ private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { long capabilities = posixCapabilitiesAsBits( - OsConstants.CAP_IPC_LOCK, - OsConstants.CAP_KILL, - OsConstants.CAP_NET_ADMIN, - OsConstants.CAP_NET_BIND_SERVICE, - OsConstants.CAP_NET_BROADCAST, - OsConstants.CAP_NET_RAW, - OsConstants.CAP_SYS_MODULE, - OsConstants.CAP_SYS_NICE, - OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_TIME, - OsConstants.CAP_SYS_TTY_CONFIG, - OsConstants.CAP_WAKE_ALARM, - OsConstants.CAP_BLOCK_SUSPEND + OsConstants.CAP_IPC_LOCK, + OsConstants.CAP_KILL, + OsConstants.CAP_NET_ADMIN, + OsConstants.CAP_NET_BIND_SERVICE, + OsConstants.CAP_NET_BROADCAST, + OsConstants.CAP_NET_RAW, + OsConstants.CAP_SYS_MODULE, + OsConstants.CAP_SYS_NICE, + OsConstants.CAP_SYS_PTRACE, + OsConstants.CAP_SYS_TIME, + OsConstants.CAP_SYS_TTY_CONFIG, + OsConstants.CAP_WAKE_ALARM, + OsConstants.CAP_BLOCK_SUSPEND ); /* Containers run without some capabilities, so drop any caps that are not available. */ StructCapUserHeader header = new StructCapUserHeader( @@ -666,38 +672,39 @@ public class ZygoteInit { /* Hardcoded command line to start the system server */ String args[] = { - "--setuid=1000", - "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", - "--capabilities=" + capabilities + "," + capabilities, - "--nice-name=system_server", - "--runtime-args", - "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, - "com.android.server.SystemServer", + "--setuid=1000", + "--setgid=1000", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," + + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", + "--capabilities=" + capabilities + "," + capabilities, + "--nice-name=system_server", + "--runtime-args", + "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, + "com.android.server.SystemServer", }; - ZygoteConnection.Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; int pid; try { - parsedArgs = new ZygoteConnection.Arguments(args); - ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); - ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); + parsedArgs = new ZygoteArguments(args); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); boolean profileSystemServer = SystemProperties.getBoolean( "dalvik.vm.profilesystemserver", false); if (profileSystemServer) { - parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; + parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } /* Request to fork the system server process */ pid = Zygote.forkSystemServer( - parsedArgs.uid, parsedArgs.gid, - parsedArgs.gids, - parsedArgs.runtimeFlags, + parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, + parsedArgs.mRuntimeFlags, null, - parsedArgs.permittedCapabilities, - parsedArgs.effectiveCapabilities); + parsedArgs.mPermittedCapabilities, + parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } @@ -743,7 +750,7 @@ public class ZygoteInit { throw new RuntimeException("Failed to setpgid(0,0)", ex); } - final Runnable caller; + Runnable caller; try { // Report Zygote start time to tron unless it is a runtime restart if (!"1".equals(SystemProperties.get("sys.boot_completed"))) { @@ -779,16 +786,26 @@ public class ZygoteInit { throw new RuntimeException("No ABI list supplied."); } - zygoteServer.registerServerSocketFromEnv(socketName); + // TODO (chriswailes): Wrap these three calls in a helper function? + final String blastulaSocketName = + socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME) + ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME + : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME; + + zygoteServer.createZygoteSocket(socketName); + Zygote.createBlastulaSocket(blastulaSocketName); + + Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)); + // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { bootTimingsTraceLog.traceBegin("ZygotePreload"); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); preload(bootTimingsTraceLog); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); bootTimingsTraceLog.traceEnd(); // ZygotePreload } else { Zygote.resetNicePriority(); @@ -822,11 +839,18 @@ public class ZygoteInit { } } - Log.i(TAG, "Accepting command socket connections"); + // If the return value is null then this is the zygote process + // returning to the normal control flow. If it returns a Runnable + // object then this is a blastula that has finished specializing. + caller = Zygote.initBlastulaPool(); + + if (caller == null) { + Log.i(TAG, "Accepting command socket connections"); - // The select loop returns early in the child process after a fork and - // loops forever in the zygote. - caller = zygoteServer.runSelectLoop(abiList); + // The select loop returns early in the child process after a fork and + // loops forever in the zygote. + caller = zygoteServer.runSelectLoop(abiList); + } } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; @@ -844,17 +868,16 @@ public class ZygoteInit { /** * Return {@code true} if this device configuration has another zygote. * - * We determine this by comparing the device ABI list with this zygotes - * list. If this zygote supports all ABIs this device supports, there won't - * be another zygote. + * We determine this by comparing the device ABI list with this zygotes list. If this zygote + * supports all ABIs this device supports, there won't be another zygote. */ private static boolean hasSecondZygote(String abiList) { return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList); } private static void waitForSecondaryZygote(String socketName) { - String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ? - Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET; + String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName) + ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME; ZygoteProcess.waitForConnectionToZygote(otherZygoteName); } @@ -869,9 +892,8 @@ public class ZygoteInit { } /** - * The main function called when started through the zygote process. This - * could be unified with main(), if the native code in nativeFinishInit() - * were rationalized with Zygote startup.<p> + * The main function called when started through the zygote process. This could be unified with + * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p> * * Current recognized args: * <ul> @@ -881,7 +903,8 @@ public class ZygoteInit { * @param targetSdkVersion target SDK version * @param argv arg strings */ - public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) { + public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, + ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } @@ -895,9 +918,9 @@ public class ZygoteInit { } /** - * The main function called when starting a child zygote process. This is used as an - * alternative to zygoteInit(), which skips calling into initialization routines that - * start the Binder threadpool. + * The main function called when starting a child zygote process. This is used as an alternative + * to zygoteInit(), which skips calling into initialization routines that start the Binder + * threadpool. */ static final Runnable childZygoteInit( int targetSdkVersion, String[] argv, ClassLoader classLoader) { diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index fecf9b9da5dd..a78c095a8deb 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -20,14 +20,16 @@ import static android.system.OsConstants.POLLIN; import android.net.LocalServerSocket; import android.net.LocalSocket; -import android.system.Os; import android.system.ErrnoException; +import android.system.Os; import android.system.StructPollfd; import android.util.Log; - import android.util.Slog; -import java.io.IOException; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.FileDescriptor; +import java.io.IOException; import java.util.ArrayList; /** @@ -40,18 +42,17 @@ import java.util.ArrayList; * client protocol. */ class ZygoteServer { + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate public static final String TAG = "ZygoteServer"; - private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; - /** * Listening socket that accepts new server connections. */ - private LocalServerSocket mServerSocket; + private LocalServerSocket mZygoteSocket; /** - * Whether or not mServerSocket's underlying FD should be closed directly. - * If mServerSocket is created with an existing FD, closing the socket does + * Whether or not mZygoteSocket's underlying FD should be closed directly. + * If mZygoteSocket is created with an existing FD, closing the socket does * not close the FD and it must be closed explicitly. If the socket is created * with a name instead, then closing the socket will close the underlying FD * and it should not be double-closed. @@ -63,39 +64,24 @@ class ZygoteServer { */ private boolean mIsForkChild; - ZygoteServer() { - } + ZygoteServer() { } void setForkChild() { mIsForkChild = true; } /** - * Registers a server socket for zygote command connections. This locates the server socket - * file descriptor through an ANDROID_SOCKET_ environment variable. + * Creates a managed object representing the Zygote socket that has already + * been initialized and bound by init. + * + * TODO (chriswailes): Move the name selection logic into this function. * * @throws RuntimeException when open fails */ - void registerServerSocketFromEnv(String socketName) { - if (mServerSocket == null) { - int fileDesc; - final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; - try { - String env = System.getenv(fullSocketName); - fileDesc = Integer.parseInt(env); - } catch (RuntimeException ex) { - throw new RuntimeException(fullSocketName + " unset or invalid", ex); - } - - try { - FileDescriptor fd = new FileDescriptor(); - fd.setInt$(fileDesc); - mServerSocket = new LocalServerSocket(fd); - mCloseSocketFd = true; - } catch (IOException ex) { - throw new RuntimeException( - "Error binding to local socket '" + fileDesc + "'", ex); - } + void createZygoteSocket(String socketName) { + if (mZygoteSocket == null) { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName); + mCloseSocketFd = true; } } @@ -104,9 +90,9 @@ class ZygoteServer { * at the specified name in the abstract socket namespace. */ void registerServerSocketAtAbstractName(String socketName) { - if (mServerSocket == null) { + if (mZygoteSocket == null) { try { - mServerSocket = new LocalServerSocket(socketName); + mZygoteSocket = new LocalServerSocket(socketName); mCloseSocketFd = false; } catch (IOException ex) { throw new RuntimeException( @@ -121,7 +107,7 @@ class ZygoteServer { */ private ZygoteConnection acceptCommandPeer(String abiList) { try { - return createNewConnection(mServerSocket.accept(), abiList); + return createNewConnection(mZygoteSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); @@ -139,9 +125,9 @@ class ZygoteServer { */ void closeServerSocket() { try { - if (mServerSocket != null) { - FileDescriptor fd = mServerSocket.getFileDescriptor(); - mServerSocket.close(); + if (mZygoteSocket != null) { + FileDescriptor fd = mZygoteSocket.getFileDescriptor(); + mZygoteSocket.close(); if (fd != null && mCloseSocketFd) { Os.close(fd); } @@ -152,7 +138,7 @@ class ZygoteServer { Log.e(TAG, "Zygote: error closing descriptor", ex); } - mServerSocket = null; + mZygoteSocket = null; } /** @@ -161,8 +147,8 @@ class ZygoteServer { * closure after a child process is forked off. */ - FileDescriptor getServerSocketFileDescriptor() { - return mServerSocket.getFileDescriptor(); + FileDescriptor getZygoteSocketFileDescriptor() { + return mZygoteSocket.getFileDescriptor(); } /** @@ -171,36 +157,67 @@ class ZygoteServer { * worth at a time. */ Runnable runSelectLoop(String abiList) { - ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); + ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); - fds.add(mServerSocket.getFileDescriptor()); + socketFDs.add(mZygoteSocket.getFileDescriptor()); peers.add(null); while (true) { - StructPollfd[] pollFds = new StructPollfd[fds.size()]; - for (int i = 0; i < pollFds.length; ++i) { - pollFds[i] = new StructPollfd(); - pollFds[i].fd = fds.get(i); - pollFds[i].events = (short) POLLIN; + int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); + + // Space for all of the socket FDs, the Blastula Pool Event FD, and + // all of the open blastula read pipe FDs. + StructPollfd[] pollFDs = + new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length]; + + int pollIndex = 0; + for (FileDescriptor socketFD : socketFDs) { + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = socketFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; } + + final int blastulaPoolEventFDIndex = pollIndex; + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + + for (int blastulaPipeFD : blastulaPipeFDs) { + FileDescriptor managedFd = new FileDescriptor(); + managedFd.setInt$(blastulaPipeFD); + + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = managedFd; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + } + try { - Os.poll(pollFds, -1); + Os.poll(pollFDs, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } - for (int i = pollFds.length - 1; i >= 0; --i) { - if ((pollFds[i].revents & POLLIN) == 0) { + + while (--pollIndex >= 0) { + if ((pollFDs[pollIndex].revents & POLLIN) == 0) { continue; } - if (i == 0) { + if (pollIndex == 0) { + // Zygote server socket + ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); - fds.add(newPeer.getFileDesciptor()); - } else { + socketFDs.add(newPeer.getFileDescriptor()); + + } else if (pollIndex < blastulaPoolEventFDIndex) { + // Session socket accepted from the Zygote server socket + try { - ZygoteConnection connection = peers.get(i); + ZygoteConnection connection = peers.get(pollIndex); final Runnable command = connection.processOneCommand(this); if (mIsForkChild) { @@ -218,12 +235,12 @@ class ZygoteServer { } // We don't know whether the remote side of the socket was closed or - // not until we attempt to read from it from processOneCommand. This shows up as - // a regular POLLIN event in our regular processing loop. + // not until we attempt to read from it from processOneCommand. This + // shows up as a regular POLLIN event in our regular processing loop. if (connection.isClosedByPeer()) { connection.closeSocket(); - peers.remove(i); - fds.remove(i); + peers.remove(pollIndex); + socketFDs.remove(pollIndex); } } } catch (Exception e) { @@ -235,13 +252,13 @@ class ZygoteServer { Slog.e(TAG, "Exception executing zygote command: ", e); - // Make sure the socket is closed so that the other end knows immediately - // that something has gone wrong and doesn't time out waiting for a - // response. - ZygoteConnection conn = peers.remove(i); + // Make sure the socket is closed so that the other end knows + // immediately that something has gone wrong and doesn't time out + // waiting for a response. + ZygoteConnection conn = peers.remove(pollIndex); conn.closeSocket(); - fds.remove(i); + socketFDs.remove(pollIndex); } else { // We're in the child so any exception caught here has happened post // fork and before we execute ActivityThread.main (or any other main() @@ -255,6 +272,55 @@ class ZygoteServer { // is returned. mIsForkChild = false; } + } else { + // Either the blastula pool event FD or a blastula reporting pipe. + + // If this is the event FD the payload will be the number of blastulas removed. + // If this is a reporting pipe FD the payload will be the PID of the blastula + // that was just specialized. + long messagePayload = -1; + + try { + byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES]; + int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length); + + if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) { + DataInputStream inputStream = + new DataInputStream(new ByteArrayInputStream(buffer)); + + messagePayload = inputStream.readLong(); + } else { + Log.e(TAG, "Incomplete read from blastula management FD of size " + + readBytes); + continue; + } + } catch (Exception ex) { + if (pollIndex == blastulaPoolEventFDIndex) { + Log.e(TAG, "Failed to read from blastula pool event FD: " + + ex.getMessage()); + } else { + Log.e(TAG, "Failed to read from blastula reporting pipe: " + + ex.getMessage()); + } + + continue; + } + + if (pollIndex > blastulaPoolEventFDIndex) { + Zygote.removeBlastulaTableEntry((int) messagePayload); + } + + int[] sessionSocketRawFDs = + socketFDs.subList(1, socketFDs.size()) + .stream() + .mapToInt(fd -> fd.getInt$()) + .toArray(); + + final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs); + + if (command != null) { + return command; + } } } } diff --git a/core/java/com/android/internal/statusbar/NotificationVisibility.java b/core/java/com/android/internal/statusbar/NotificationVisibility.java index a7203e7e17d2..24bb789c3cc5 100644 --- a/core/java/com/android/internal/statusbar/NotificationVisibility.java +++ b/core/java/com/android/internal/statusbar/NotificationVisibility.java @@ -21,6 +21,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import java.util.ArrayDeque; import java.util.Collection; @@ -33,18 +35,53 @@ public class NotificationVisibility implements Parcelable { public int rank; public int count; public boolean visible = true; + /** The visible location of the notification, could be e.g. notification shade or HUN. */ + public NotificationLocation location; /*package*/ int id; + /** + * The UI location of the notification. + * + * There is a one-to-one mapping between this enum and + * MetricsProto.MetricsEvent.NotificationLocation. + */ + public enum NotificationLocation { + LOCATION_UNKNOWN(MetricsEvent.LOCATION_UNKNOWN), + LOCATION_FIRST_HEADS_UP(MetricsEvent.LOCATION_FIRST_HEADS_UP), // visible heads-up + LOCATION_HIDDEN_TOP(MetricsEvent.LOCATION_HIDDEN_TOP), // hidden/scrolled away on the top + LOCATION_MAIN_AREA(MetricsEvent.LOCATION_MAIN_AREA), // visible in the shade + // in the bottom stack, and peeking + LOCATION_BOTTOM_STACK_PEEKING(MetricsEvent.LOCATION_BOTTOM_STACK_PEEKING), + // in the bottom stack, and hidden + LOCATION_BOTTOM_STACK_HIDDEN(MetricsEvent.LOCATION_BOTTOM_STACK_HIDDEN), + LOCATION_GONE(MetricsEvent.LOCATION_GONE); // the view isn't laid out at all + + private final int mMetricsEventNotificationLocation; + + NotificationLocation(int metricsEventNotificationLocation) { + mMetricsEventNotificationLocation = metricsEventNotificationLocation; + } + + /** + * Returns the field from MetricsEvent.NotificationLocation that corresponds to this object. + */ + public int toMetricsEventEnum() { + return mMetricsEventNotificationLocation; + } + } + private NotificationVisibility() { id = sNexrId++; } - private NotificationVisibility(String key, int rank, int count, boolean visibile) { + private NotificationVisibility(String key, int rank, int count, boolean visible, + NotificationLocation location) { this(); this.key = key; this.rank = rank; this.count = count; - this.visible = visibile; + this.visible = visible; + this.location = location; } @Override @@ -54,12 +91,13 @@ public class NotificationVisibility implements Parcelable { + " rank=" + rank + " count=" + count + (visible?" visible":"") + + " location=" + location.name() + " )"; } @Override public NotificationVisibility clone() { - return obtain(this.key, this.rank, this.count, this.visible); + return obtain(this.key, this.rank, this.count, this.visible, this.location); } @Override @@ -89,6 +127,7 @@ public class NotificationVisibility implements Parcelable { out.writeInt(this.rank); out.writeInt(this.count); out.writeInt(this.visible ? 1 : 0); + out.writeString(this.location.name()); } private void readFromParcel(Parcel in) { @@ -96,18 +135,28 @@ public class NotificationVisibility implements Parcelable { this.rank = in.readInt(); this.count = in.readInt(); this.visible = in.readInt() != 0; + this.location = NotificationLocation.valueOf(in.readString()); } /** - * Return a new NotificationVisibility instance from the global pool. Allows us to - * avoid allocating new objects in many cases. + * Create a new NotificationVisibility object. */ public static NotificationVisibility obtain(String key, int rank, int count, boolean visible) { + return obtain(key, rank, count, visible, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); + } + + /** + * Create a new NotificationVisibility object. + */ + public static NotificationVisibility obtain(String key, int rank, int count, boolean visible, + NotificationLocation location) { NotificationVisibility vo = obtain(); vo.key = key; vo.rank = rank; vo.count = count; vo.visible = visible; + vo.location = location; return vo; } diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index 240c2e757faf..32601368f2fb 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -196,6 +196,15 @@ public class DumpUtils { } } + private static void writeContaminantPresenceStatus(@NonNull DualDumpOutputStream dump, + @NonNull String idName, long id, int contaminantPresenceStatus) { + if (dump.isProto()) { + dump.write(idName, id, contaminantPresenceStatus); + } else { + dump.write(idName, id, + UsbPort.contaminantPresenceStatusToString(contaminantPresenceStatus)); + } + } public static void writePortStatus(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id, @NonNull UsbPortStatus status) { @@ -232,6 +241,10 @@ public class DumpUtils { dump.end(roleCombinationToken); } + writeContaminantPresenceStatus(dump, "contaminant_presence_status", + UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS, + status.getContaminantDetectionStatus()); + dump.end(token); } } diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 97d5a657e5c4..2ee902ab6468 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -40,7 +40,7 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods, - in EditorInfo attribute, boolean restarting); + in EditorInfo attribute, boolean restarting, boolean preRenderImeViews); void createSession(in InputChannel channel, IInputSessionCallback callback); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 8194a920d331..0752efe562a9 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -64,7 +64,6 @@ interface IInputMethodManager { void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); boolean isInputMethodPickerShownForTest(); InputMethodSubtype getCurrentInputMethodSubtype(); - boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); // This is kept due to @UnsupportedAppUsage. // TODO(Bug 113914148): Consider removing this. diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index d5dc703408e4..8d3c482104f7 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -34,6 +34,7 @@ import android.app.trust.TrustManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.AsyncTask; import android.os.Handler; @@ -176,6 +177,7 @@ public class LockPatternUtils { private UserManager mUserManager; private final Handler mHandler; private final SparseLongArray mLockoutDeadlines = new SparseLongArray(); + private Boolean mHasSecureLockScreen; /** * Use {@link TrustManager#isTrustUsuallyManaged(int)}. @@ -706,6 +708,10 @@ public class LockPatternUtils { * @param userId the user whose pattern is to be saved. */ public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) { throw new IllegalArgumentException("pattern must not be null and at least " + MIN_LOCK_PATTERN_SIZE + " dots long."); @@ -801,6 +807,10 @@ public class LockPatternUtils { /** Update the encryption password if it is enabled **/ private void updateEncryptionPassword(final int type, final String password) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (!isDeviceEncryptionEnabled()) { return; } @@ -835,6 +845,10 @@ public class LockPatternUtils { */ public void saveLockPassword(String password, String savedPassword, int requestedQuality, int userHandle) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) { throw new IllegalArgumentException("password must not be null and at least " + "of length " + MIN_LOCK_PASSWORD_SIZE); @@ -1621,6 +1635,10 @@ public class LockPatternUtils { */ public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality, long tokenHandle, byte[] token, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } LockSettingsInternal localService = getLockSettingsInternal(); if (type != CREDENTIAL_TYPE_NONE) { if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { @@ -1854,6 +1872,17 @@ public class LockPatternUtils { return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0; } + /** + * Return true if the device supports the lock screen feature, false otherwise. + */ + public boolean hasSecureLockScreen() { + if (mHasSecureLockScreen == null) { + mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)); + } + return mHasSecureLockScreen.booleanValue(); + } + public static boolean userOwnsFrpCredential(Context context, UserInfo info) { return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context); } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 841e5b679f5f..3537465db682 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -140,6 +140,10 @@ public class SystemConfig { // without throttling, as read from the configuration files. final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>(); + // These are the packages that are white-listed to be able to retrieve location even when user + // location settings are off, for emergency purposes, as read from the configuration files. + final ArraySet<String> mAllowIgnoreLocationSettings = new ArraySet<>(); + // These are the action strings of broadcasts which are whitelisted to // be delivered anonymously even to apps which target O+. final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>(); @@ -255,6 +259,10 @@ public class SystemConfig { return mAllowUnthrottledLocation; } + public ArraySet<String> getAllowIgnoreLocationSettings() { + return mAllowIgnoreLocationSettings; + } + public ArraySet<String> getLinkedApps() { return mLinkedApps; } @@ -682,6 +690,20 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "allow-ignore-location-settings": { + if (allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowIgnoreLocationSettings.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; case "allow-implicit-broadcast": { if (allowAll) { String action = parser.getAttributeValue(null, "action"); diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 18d9b5aeb1c8..f458299468b1 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -152,6 +152,8 @@ extern int register_android_graphics_text_MeasuredText(JNIEnv* env); extern int register_android_graphics_text_LineBreaker(JNIEnv *env); extern int register_android_view_DisplayEventReceiver(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); +extern int register_android_view_InputApplicationHandle(JNIEnv* env); +extern int register_android_view_InputWindowHandle(JNIEnv* env); extern int register_android_view_TextureLayer(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_RenderNodeAnimator(JNIEnv* env); @@ -1370,6 +1372,8 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_RenderNode), REG_JNI(register_android_view_RenderNodeAnimator), REG_JNI(register_android_view_DisplayListCanvas), + REG_JNI(register_android_view_InputApplicationHandle), + REG_JNI(register_android_view_InputWindowHandle), REG_JNI(register_android_view_TextureLayer), REG_JNI(register_android_view_ThreadedRenderer), REG_JNI(register_android_view_Surface), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index ad51c4701d84..5de088397690 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -19,6 +19,7 @@ #include <hwui/Paint.h> #include <hwui/Bitmap.h> #include <renderthread/RenderProxy.h> +#include <utils/Color.h> #include <android_runtime/android_hardware_HardwareBuffer.h> @@ -602,6 +603,14 @@ static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { return static_cast<jint>(bitmap->getGenerationID()); } +static jboolean Bitmap_isConfigF16(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->info().colorType() == kRGBA_F16_SkColorType) { + return JNI_TRUE; + } + return JNI_FALSE; +} + static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); if (bitmap->info().alphaType() == kPremul_SkAlphaType) { @@ -1120,7 +1129,8 @@ static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphic sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); // To support any color space, we need to pass an additional ColorSpace argument to // java Bitmap.createHardwareBitmap. - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()); + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, SkColorSpace::MakeSRGB()); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from graphic buffer"); return NULL; @@ -1133,7 +1143,8 @@ static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject har AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, hardwareBuffer); sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf)); - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from hardware buffer"); @@ -1193,6 +1204,7 @@ static const JNINativeMethod gBitmapMethods[] = { { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong }, { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes }, { "nativeConfig", "(J)I", (void*)Bitmap_config }, + { "nativeIsConfigF16", "(J)Z", (void*)Bitmap_isConfigF16 }, { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha }, { "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied}, { "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha}, diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 7679c5b63274..acb34ba3dfec 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -28,6 +28,8 @@ #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" +#include "SkFont.h" +#include "SkFontMetrics.h" #include "SkFontTypes.h" #include "SkMaskFilter.h" #include "SkPath.h" @@ -69,9 +71,21 @@ static JMetricsID gFontMetrics_fieldID; static jclass gFontMetricsInt_class; static JMetricsID gFontMetricsInt_fieldID; -static void defaultSettingsForAndroid(Paint* paint) { - // GlyphID encoding is required because we are using Harfbuzz shaping - paint->setTextEncoding(kGlyphID_SkTextEncoding); +static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, + const SkPoint pos[], SkPath* dst) { + struct Rec { + SkPath* fDst; + const SkPoint* fPos; + } rec = { dst, pos }; + font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) { + Rec* rec = (Rec*)ctx; + if (src) { + SkMatrix tmp(mx); + tmp.postTranslate(rec->fPos->fX, rec->fPos->fY); + rec->fDst->addPath(*src, tmp); + } + rec->fPos += 1; + }, &rec); } namespace PaintGlue { @@ -88,18 +102,7 @@ namespace PaintGlue { } static jlong init(JNIEnv* env, jobject) { - static_assert(1 << 0 == SkPaint::kAntiAlias_Flag, "paint_flags_mismatch"); - static_assert(1 << 2 == SkPaint::kDither_Flag, "paint_flags_mismatch"); - static_assert(1 << 3 == SkPaint::kUnderlineText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 5 == SkPaint::kFakeBoldText_Flag, "paint_flags_mismatch"); - static_assert(1 << 6 == SkPaint::kLinearText_Flag, "paint_flags_mismatch"); - static_assert(1 << 7 == SkPaint::kSubpixelText_Flag, "paint_flags_mismatch"); - static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag, "paint_flags_mismatch"); - - Paint* obj = new Paint(); - defaultSettingsForAndroid(obj); - return reinterpret_cast<jlong>(obj); + return reinterpret_cast<jlong>(new Paint); } static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) { @@ -288,10 +291,11 @@ namespace PaintGlue { pos[i].fX = x + layout.getX(i); pos[i].fY = y + layout.getY(i); } + const SkFont& font = paint->getSkFont(); if (start == 0) { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path); + getPosTextPath(font, glyphs, end, pos, path); } else { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath); + getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath); path->addPath(tmpPath); } } @@ -321,7 +325,6 @@ namespace PaintGlue { x += MinikinUtils::xOffsetForTextAlign(paint, layout); Paint::Align align = paint->getTextAlign(); paint->setTextAlign(Paint::kLeft_Align); - paint->setTextEncoding(kGlyphID_SkTextEncoding); GetTextFunctor f(layout, path, x, y, paint, glyphs, pos); MinikinUtils::forFontRun(layout, paint, f); paint->setTextAlign(align); @@ -584,20 +587,21 @@ namespace PaintGlue { const int kElegantDescent = -500; const int kElegantLeading = 0; Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SkFont* font = &paint->getSkFont(); const Typeface* typeface = paint->getAndroidTypeface(); typeface = Typeface::resolveDefault(typeface); minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle); - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); - MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery); - SkScalar spacing = paint->getFontMetrics(metrics); + float saveSkewX = font->getSkewX(); + bool savefakeBold = font->isEmbolden(); + MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery); + SkScalar spacing = font->getMetrics(metrics); // The populateSkPaint call may have changed fake bold / text skew // because we want to measure with those effects applied, so now // restore the original settings. - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + font->setSkewX(saveSkewX); + font->setEmbolden(savefakeBold); if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) { - SkScalar size = paint->getTextSize(); + SkScalar size = font->getSize(); metrics->fTop = -size * kElegantTop / 2048; metrics->fBottom = -size * kElegantBottom / 2048; metrics->fAscent = -size * kElegantAscent / 2048; @@ -646,9 +650,7 @@ namespace PaintGlue { // ------------------ @CriticalNative --------------------------- static void reset(jlong objHandle) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - obj->reset(); - defaultSettingsForAndroid(obj); + reinterpret_cast<Paint*>(objHandle)->reset(); } static void assign(jlong dstPaintHandle, jlong srcPaintHandle) { @@ -657,31 +659,13 @@ namespace PaintGlue { *dst = *src; } - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - static jint getFlags(jlong paintHandle) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - uint32_t result = nativePaint->getFlags(); - result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away. - if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) { - result |= sFilterBitmapFlag; - } - return static_cast<jint>(result); + uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags(); + return static_cast<jint>(flags); } static void setFlags(jlong paintHandle, jint flags) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - // Instead of modifying 0x02, change the filter level. - nativePaint->setFilterQuality(flags & sFilterBitmapFlag - ? kLow_SkFilterQuality - : kNone_SkFilterQuality); - // Don't pass through filter flag, which is no longer stored in paint's flags. - flags &= ~sFilterBitmapFlag; - // Use the existing value for 0x02. - const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag; - flags |= existing0x02Flag; - nativePaint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags); } static jint getHinting(jlong paintHandle) { @@ -699,37 +683,23 @@ namespace PaintGlue { } static void setLinearText(jlong paintHandle, jboolean linearText) { - reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText); } static void setSubpixelText(jlong paintHandle, jboolean subpixelText) { - reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText); } static void setUnderlineText(jlong paintHandle, jboolean underlineText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (underlineText) { - flags |= Paint::kUnderlineText_ReserveFlag; - } else { - flags &= ~Paint::kUnderlineText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText); } static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (strikeThruText) { - flags |= Paint::kStrikeThruText_ReserveFlag; - } else { - flags &= ~Paint::kStrikeThruText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText); } static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) { - reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText); } static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) { @@ -907,27 +877,29 @@ namespace PaintGlue { } static jfloat getTextSize(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize()); } static void setTextSize(jlong paintHandle, jfloat textSize) { - reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize); + if (textSize >= 0) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize); + } } static jfloat getTextScaleX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX()); } static void setTextScaleX(jlong paintHandle, jfloat scaleX) { - reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX); } static jfloat getTextSkewX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX()); } static void setTextSkewX(jlong paintHandle, jfloat skewX) { - reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX); } static jfloat getLetterSpacing(jlong paintHandle) { @@ -979,7 +951,7 @@ namespace PaintGlue { if (metrics.hasUnderlinePosition(&position)) { return SkScalarToFloat(position); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Top * textSize); } } @@ -991,18 +963,18 @@ namespace PaintGlue { if (metrics.hasUnderlineThickness(&thickness)) { return SkScalarToFloat(thickness); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize); } } static jfloat getStrikeThruPosition(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); } static jfloat getStrikeThruThickness(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp index 182b22b3c917..4fe9140572d3 100644 --- a/core/jni/android/graphics/PaintFilter.cpp +++ b/core/jni/android/graphics/PaintFilter.cpp @@ -21,6 +21,7 @@ #include "core_jni_helpers.h" +#include "hwui/Paint.h" #include "hwui/PaintFilter.h" #include "SkPaint.h" @@ -29,11 +30,15 @@ namespace android { class PaintFlagsFilter : public PaintFilter { public: PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) { - fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags); - fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags); + fClearFlags = static_cast<uint16_t>(clearFlags); + fSetFlags = static_cast<uint16_t>(setFlags); } void filter(SkPaint* paint) override { - paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags); + uint32_t flags = Paint::GetSkPaintJavaFlags(*paint); + Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags); + } + void filterFullPaint(Paint* paint) override { + paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags); } private: @@ -41,33 +46,6 @@ private: uint16_t fSetFlags; }; -// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality. -class CompatPaintFlagsFilter : public PaintFlagsFilter { -public: - CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality) - : PaintFlagsFilter(clearFlags, setFlags) - , fDesiredQuality(desiredQuality) { - } - - virtual void filter(SkPaint* paint) { - PaintFlagsFilter::filter(paint); - paint->setFilterQuality(fDesiredQuality); - } - -private: - const SkFilterQuality fDesiredQuality; -}; - -// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it. -static inline bool hadFiltering(jint& flags) { - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - - const bool result = (flags & sFilterBitmapFlag) != 0; - flags &= ~sFilterBitmapFlag; - return result; -} - class PaintFilterGlue { public: @@ -78,29 +56,11 @@ public: static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz, jint clearFlags, jint setFlags) { + PaintFilter* filter = nullptr; if (clearFlags | setFlags) { - // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no - // longer has a Skia equivalent flag (instead it corresponds to - // calling setFilterQuality), and keep track of which group(s), if - // any, had the flag set. - const bool turnFilteringOn = hadFiltering(setFlags); - const bool turnFilteringOff = hadFiltering(clearFlags); - - PaintFilter* filter; - if (turnFilteringOn) { - // Turning filtering on overrides turning it off. - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kLow_SkFilterQuality); - } else if (turnFilteringOff) { - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kNone_SkFilterQuality); - } else { - filter = new PaintFlagsFilter(clearFlags, setFlags); - } - return reinterpret_cast<jlong>(filter); - } else { - return NULL; + filter = new PaintFlagsFilter(clearFlags, setFlags); } + return reinterpret_cast<jlong>(filter); } }; diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index fd1d87ff62f6..d29857d0cf12 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -37,6 +37,12 @@ Picture::Picture(const Picture* src) { } } +Picture::Picture(sk_sp<SkPicture>&& src) { + mPicture = std::move(src); + mWidth = 0; + mHeight = 0; +} + Canvas* Picture::beginRecording(int width, int height) { mPicture.reset(NULL); mRecorder.reset(new SkPictureRecorder); diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h index 306863174334..536f651473a9 100644 --- a/core/jni/android/graphics/Picture.h +++ b/core/jni/android/graphics/Picture.h @@ -37,6 +37,7 @@ class Canvas; class Picture { public: explicit Picture(const Picture* src = NULL); + explicit Picture(sk_sp<SkPicture>&& src); Canvas* beginRecording(int width, int height); diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp index 10005ddcb5ef..1ab58432c686 100644 --- a/core/jni/android_hardware_input_InputApplicationHandle.cpp +++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp @@ -93,7 +93,7 @@ bool NativeInputApplicationHandle::updateInfo() { // --- Global functions --- -sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( +sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle( JNIEnv* env, jobject inputApplicationHandleObj) { if (!inputApplicationHandleObj) { return NULL; @@ -108,7 +108,7 @@ sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( } else { jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj); handle = new NativeInputApplicationHandle(objWeak); - handle->incStrong((void*)android_server_InputApplicationHandle_getHandle); + handle->incStrong((void*)android_view_InputApplicationHandle_getHandle); env->SetLongField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr, reinterpret_cast<jlong>(handle)); } @@ -118,7 +118,7 @@ sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( // --- JNI --- -static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) { +static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) { AutoMutex _l(gHandleMutex); jlong ptr = env->GetLongField(obj, gInputApplicationHandleClassInfo.ptr); @@ -126,7 +126,7 @@ static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, job env->SetLongField(obj, gInputApplicationHandleClassInfo.ptr, 0); NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr); - handle->decStrong((void*)android_server_InputApplicationHandle_getHandle); + handle->decStrong((void*)android_view_InputApplicationHandle_getHandle); } } @@ -134,7 +134,7 @@ static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, job static const JNINativeMethod gInputApplicationHandleMethods[] = { /* name, signature, funcPtr */ { "nativeDispose", "()V", - (void*) android_server_InputApplicationHandle_nativeDispose }, + (void*) android_view_InputApplicationHandle_nativeDispose }, }; #define FIND_CLASS(var, className) \ @@ -145,7 +145,7 @@ static const JNINativeMethod gInputApplicationHandleMethods[] = { var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find field " fieldName); -int register_android_server_InputApplicationHandle(JNIEnv* env) { +int register_android_view_InputApplicationHandle(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle", gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); (void) res; // Faked use when LOG_NDEBUG. diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h index 711561150e51..5abeab454141 100644 --- a/core/jni/android_hardware_input_InputApplicationHandle.h +++ b/core/jni/android_hardware_input_InputApplicationHandle.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H -#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H +#ifndef _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H +#define _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H #include <string> @@ -40,9 +40,9 @@ private: }; -extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle( +extern sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle( JNIEnv* env, jobject inputApplicationHandleObj); } // namespace android -#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H +#endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index 76920f57ed08..c0e45b12cf76 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -159,7 +159,7 @@ bool NativeInputWindowHandle::updateInfo() { gInputWindowHandleClassInfo.inputApplicationHandle); if (inputApplicationHandleObj) { sp<InputApplicationHandle> inputApplicationHandle = - android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj); + android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj); if (inputApplicationHandle != nullptr) { inputApplicationHandle->updateInfo(); mInfo.applicationInfo = *(inputApplicationHandle->getInfo()); @@ -174,7 +174,7 @@ bool NativeInputWindowHandle::updateInfo() { // --- Global functions --- -sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( +sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle( JNIEnv* env, jobject inputWindowHandleObj) { if (!inputWindowHandleObj) { return NULL; @@ -189,7 +189,7 @@ sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( } else { jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj); handle = new NativeInputWindowHandle(objWeak); - handle->incStrong((void*)android_server_InputWindowHandle_getHandle); + handle->incStrong((void*)android_view_InputWindowHandle_getHandle); env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr, reinterpret_cast<jlong>(handle)); } @@ -199,7 +199,7 @@ sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( // --- JNI --- -static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) { +static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) { AutoMutex _l(gHandleMutex); jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr); @@ -207,7 +207,7 @@ static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0); NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr); - handle->decStrong((void*)android_server_InputWindowHandle_getHandle); + handle->decStrong((void*)android_view_InputWindowHandle_getHandle); } } @@ -215,7 +215,7 @@ static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject static const JNINativeMethod gInputWindowHandleMethods[] = { /* name, signature, funcPtr */ { "nativeDispose", "()V", - (void*) android_server_InputWindowHandle_nativeDispose }, + (void*) android_view_InputWindowHandle_nativeDispose }, }; #define FIND_CLASS(var, className) \ @@ -226,7 +226,7 @@ static const JNINativeMethod gInputWindowHandleMethods[] = { var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find field " fieldName); -int register_android_server_InputWindowHandle(JNIEnv* env) { +int register_android_view_InputWindowHandle(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle", gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods)); (void) res; // Faked use when LOG_NDEBUG. diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h index 54b89f5b7918..de5bd6ef97f4 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.h +++ b/core/jni/android_hardware_input_InputWindowHandle.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H -#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H +#ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H +#define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H #include <input/InputWindow.h> @@ -38,9 +38,9 @@ private: }; -extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle( +extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle( JNIEnv* env, jobject inputWindowHandleObj); } // namespace android -#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H +#endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp index 38f7a7e25389..3204317cab68 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.cpp +++ b/core/jni/android_media_MediaMetricsJNI.cpp @@ -1,90 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <android_runtime/AndroidRuntime.h> -#include <jni.h> -#include <nativehelper/JNIHelp.h> - -#include "android_media_MediaMetricsJNI.h" -#include <media/MediaAnalyticsItem.h> - - -namespace android { - -// place the attributes into a java PersistableBundle object -jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) { - - jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); - if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); - return NULL; - } - // sometimes the caller provides one for us to fill - if (mybundle == NULL) { - // create the bundle - jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); - mybundle = env->NewObject(clazzBundle, constructID); - if (mybundle == NULL) { - return NULL; - } - } - - // grab methods that we can invoke - jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); - jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); - jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); - jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); - - // env, class, method, {parms} - //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint); - - // iterate through my attributes - // -- get name, get type, get value - // -- insert appropriately into the bundle - for (size_t i = 0 ; i < item->mPropCount; i++ ) { - MediaAnalyticsItem::Prop *prop = &item->mProps[i]; - // build the key parameter from prop->mName - jstring keyName = env->NewStringUTF(prop->mName); - // invoke the appropriate method to insert - switch (prop->mType) { - case MediaAnalyticsItem::kTypeInt32: - env->CallVoidMethod(mybundle, setIntID, - keyName, (jint) prop->u.int32Value); - break; - case MediaAnalyticsItem::kTypeInt64: - env->CallVoidMethod(mybundle, setLongID, - keyName, (jlong) prop->u.int64Value); - break; - case MediaAnalyticsItem::kTypeDouble: - env->CallVoidMethod(mybundle, setDoubleID, - keyName, (jdouble) prop->u.doubleValue); - break; - case MediaAnalyticsItem::kTypeCString: - env->CallVoidMethod(mybundle, setStringID, keyName, - env->NewStringUTF(prop->u.CStringValue)); - break; - default: - ALOGE("to_String bad item type: %d for %s", - prop->mType, prop->mName); - break; - } - } - - return mybundle; -} - -}; // namespace android - +../../media/jni/android_media_MediaMetricsJNI.cpp
\ No newline at end of file diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h index b3cb4d293399..c7a685beb7e5 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.h +++ b/core/jni/android_media_MediaMetricsJNI.h @@ -1,33 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ -#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ - -#include <jni.h> -#include <nativehelper/JNIHelp.h> -#include <media/MediaAnalyticsItem.h> - -namespace android { - -class MediaMetricsJNI { -public: - static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); -}; - -}; // namespace android - -#endif // _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ +../../media/jni/android_media_MediaMetricsJNI.h
\ No newline at end of file diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 9b138ebb760a..7eddcfe425d3 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -16,8 +16,11 @@ #define LOG_TAG "NetUtils" +#include <vector> + #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include "NetdClient.h" #include <utils/misc.h> #include <android_runtime/AndroidRuntime.h> @@ -55,6 +58,31 @@ static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udp static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this. +constexpr int MAXCMDSIZE = 1024; + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "<init>", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast<jthrowable>(exception)); +} + static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { struct sock_filter filter_code[] = { @@ -372,6 +400,63 @@ static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray } } +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector<char> queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + int rcode; + std::vector<uint8_t> buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast<jbyte*>(buf.data())); + } + + return answer; +} // ---------------------------------------------------------------------------- @@ -391,6 +476,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0453195e6a1d..f1b259e10cf5 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -124,7 +124,7 @@ static jlong nativeGetNativeTransactionFinalizer(JNIEnv* env, jclass clazz) { static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, - jint windowType, jint ownerUid) { + jobject metadataParcel) { ScopedUtfChars name(env, nameStr); sp<SurfaceComposerClient> client; if (sessionObj != NULL) { @@ -134,8 +134,18 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, } SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface; + LayerMetadata metadata; + Parcel* parcel = parcelForJavaObject(env, metadataParcel); + if (parcel && !parcel->objectsCount()) { + status_t err = metadata.readFromParcel(parcel); + if (err != NO_ERROR) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Metadata parcel has wrong format"); + } + } + status_t err = client->createSurfaceChecked( - String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid); + String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata)); if (err == NAME_NOT_FOUND) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return 0; @@ -360,7 +370,7 @@ static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactio jlong nativeObject, jobject inputWindow) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle( + sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle( env, inputWindow); handle->updateInfo(); @@ -377,6 +387,28 @@ static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactio transaction->transferTouchFocus(fromToken, toToken); } +static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint id, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (!parcel) { + jniThrowNullPointerException(env, "attribute data"); + return; + } + if (parcel->objectsCount()) { + jniThrowException(env, "java/lang/RuntimeException", + "Tried to marshall a Parcel that contained Binder objects."); + return; + } + + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + std::vector<uint8_t> byteData(parcel->dataSize()); + memcpy(byteData.data(), parcel->data(), parcel->dataSize()); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setMetadata(ctrl, id, std::move(byteData)); +} + static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jfloatArray fColor) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -981,7 +1013,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz, // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { - {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J", + {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", (void*)nativeCreate }, {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel }, @@ -1099,6 +1131,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetInputWindowInfo }, {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V", (void*)nativeTransferTouchFocus }, + {"nativeSetMetadata", "(JILandroid/os/Parcel;)V", + (void*)nativeSetMetadata }, {"nativeGetDisplayedContentSamplingAttributes", "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;", (void*)nativeGetDisplayedContentSamplingAttributes }, diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 5a8ab3c1bdc4..40529191a42c 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -48,6 +48,7 @@ #include <FrameInfo.h> #include <FrameMetricsObserver.h> #include <IContextFactory.h> +#include <Picture.h> #include <Properties.h> #include <PropertyValuesAnimatorSet.h> #include <RenderNode.h> @@ -71,6 +72,11 @@ struct { } gFrameMetricsObserverClassInfo; struct { + jclass clazz; + jmethodID invokePictureCapturedCallback; +} gHardwareRenderer; + +struct { jmethodID onFrameDraw; } gFrameDrawingCallback; @@ -905,6 +911,27 @@ private: jobject mObject; }; +static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env, + jobject clazz, jlong proxyPtr, jobject pictureCallback) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + if (!pictureCallback) { + proxy->setPictureCapturedCallback(nullptr); + } else { + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, + env->NewGlobalRef(pictureCallback)); + proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) { + JNIEnv* env = getenv(globalCallbackRef->vm()); + Picture* wrapper = new Picture{std::move(picture)}; + env->CallStaticVoidMethod(gHardwareRenderer.clazz, + gHardwareRenderer.invokePictureCapturedCallback, + static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)), + globalCallbackRef->object()); + }); + } +} + static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, jobject clazz, jlong proxyPtr, jobject frameCallback) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); @@ -1011,8 +1038,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( // Continue I guess? } + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace); - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, cs); return bitmap::createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Premultiplied); } @@ -1145,6 +1173,8 @@ static const JNINativeMethod gMethods[] = { { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode}, { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode}, { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds}, + { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V", + (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI }, { "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V", (void*)android_view_ThreadedRenderer_setFrameCallback}, { "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V", @@ -1198,6 +1228,13 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie( env, metricsClass, "mTimingData", "[J"); + jclass hardwareRenderer = FindClassOrDie(env, + "android/graphics/HardwareRenderer"); + gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer)); + gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer, + "invokePictureCapturedCallback", + "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V"); + jclass frameCallbackClass = FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameDrawingCallback"); gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 2e7184b4f0fb..6ee960668a3e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -821,7 +821,7 @@ static bool NeedsNoRandomizeWorkaround() { // Utility to close down the Zygote socket file descriptors while // the child is still running as root with Zygote's privileges. Each -// descriptor (if any) is closed via dup2(), replacing it with a valid +// descriptor (if any) is closed via dup3(), replacing it with a valid // (open) descriptor to /dev/null. static void DetachDescriptors(JNIEnv* env, @@ -829,15 +829,15 @@ static void DetachDescriptors(JNIEnv* env, fail_fn_t fail_fn) { if (fds_to_close.size() > 0) { - android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR)); + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC)); if (devnull_fd == -1) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } for (int fd : fds_to_close) { ALOGV("Switching descriptor %d to /dev/null", fd); - if (dup2(devnull_fd, fd) == -1) { - fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno))); + if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) { + fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno))); } } } @@ -1472,7 +1472,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end()); fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end()); -// fds_to_close.push_back(gBlastulaPoolSocketFD); + fds_to_close.push_back(gBlastulaPoolSocketFD); if (gBlastulaPoolEventFD != -1) { fds_to_close.push_back(gBlastulaPoolEventFD); @@ -1498,7 +1498,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), fds_to_ignore(fds_to_close); -// fds_to_close.push_back(gBlastulaPoolSocketFD); + fds_to_close.push_back(gBlastulaPoolSocketFD); if (gBlastulaPoolEventFD != -1) { fds_to_close.push_back(gBlastulaPoolEventFD); diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 53dde80edd89..4b37f13cbb33 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -72,6 +72,7 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Framework jars are allowed. static const char* kFrameworksPrefix = "/system/framework/"; static const char* kJarSuffix = ".jar"; if (android::base::StartsWith(path, kFrameworksPrefix) @@ -79,6 +80,13 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Jars from the runtime apex are allowed. + static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/"; + if (android::base::StartsWith(path, kRuntimeApexPrefix) + && android::base::EndsWith(path, kJarSuffix)) { + return true; + } + // Whitelist files needed for Runtime Resource Overlay, like these: // /system/vendor/overlay/framework-res.apk // /system/vendor/overlay-subdir/pg/framework-res.apk @@ -415,13 +423,13 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { } void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { - const int dev_null_fd = open("/dev/null", O_RDWR); + const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); if (dev_null_fd < 0) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - if (dup2(dev_null_fd, fd) == -1) { - fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", + if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) { + fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s", fd, strerror(errno))); } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index f68c760a9dbb..eb716ac280e2 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2179,4 +2179,14 @@ enum PageId { // OPEN: Settings > Network & internet > Click Mobile network to land on a page with a list of // SIM/eSIM subscriptions. MOBILE_NETWORK_LIST = 1627; + + // OPEN: Settings > Display > Adaptive sleep + // OS: Q + SETTINGS_ADAPTIVE_SLEEP = 1628; + + // OPEN: Settings > System > Aware + SETTINGS_AWARE = 1632; + + // OPEN: Settings > System > Aware > Disable > Dialog + DIALOG_AWARE_DISABLE = 1633; } diff --git a/core/proto/android/debug/enums.proto b/core/proto/android/debug/enums.proto new file mode 100644 index 000000000000..6747bb7276b3 --- /dev/null +++ b/core/proto/android/debug/enums.proto @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.debug; + +option java_outer_classname = "AdbProtoEnums"; +option java_multiple_files = true; + +/** + * adb connection state used to track adb connection changes in AdbDebuggingManager.java. + */ +enum AdbConnectionStateEnum { + UNKNOWN = 0; + + /** + * The adb connection is waiting for approval from the user. + */ + AWAITING_USER_APPROVAL = 1; + + /** + * The user allowed the adb connection from the system. + */ + USER_ALLOWED = 2; + + /** + * The user denied the adb connection from the system. + */ + USER_DENIED = 3; + + /** + * The adb connection was automatically allowed without user interaction due to the system + * being previously allowed by the user with the 'always allow' option selected, and the adb + * grant has not yet expired. + */ + AUTOMATICALLY_ALLOWED = 4; + + /** + * An empty or invalid base64 encoded key was provided to the framework; the connection was + * automatically denied. + */ + DENIED_INVALID_KEY = 5; + + /** + * vold decrypt has not yet occurred; the connection was automatically denied. + */ + DENIED_VOLD_DECRYPT = 6; + + /** + * The adb session has been disconnected. + */ + DISCONNECTED = 7; +} + diff --git a/core/proto/android/hardware/biometrics/enums.proto b/core/proto/android/hardware/biometrics/enums.proto new file mode 100644 index 000000000000..91f2acbbaf03 --- /dev/null +++ b/core/proto/android/hardware/biometrics/enums.proto @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.hardware.biometrics; + +option java_outer_classname = "BiometricsProtoEnums"; +option java_multiple_files = true; + +// Logging constants for <Biometric>Service and BiometricService + +enum ModalityEnum { + MODALITY_UNKNOWN = 0; + MODALITY_FINGERPRINT = 1; // 1 << 0 + MODALITY_IRIS = 2; // 1 << 1 + MODALITY_FACE = 4; // 1 << 2 +} + +enum ClientEnum { + CLIENT_UNKNOWN = 0; + CLIENT_KEYGUARD = 1; + CLIENT_BIOMETRIC_PROMPT = 2; + CLIENT_FINGERPRINT_MANAGER = 3; // Deprecated API before BiometricPrompt was introduced +} + +enum ActionEnum { + ACTION_UNKNOWN = 0; + ACTION_ENROLL = 1; + ACTION_AUTHENTICATE = 2; + ACTION_ENUMERATE = 3; + ACTION_REMOVE = 4; +}
\ No newline at end of file diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 415857771899..7e7942e6ddf1 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -449,6 +449,10 @@ message GlobalSettingsProto { optional SettingProto gup_dev_opt_out_apps = 10; // GUP - List of Apps that are forbidden to use Game Update Package optional SettingProto gup_blacklist = 11; + // List of Apps that are allowed to use Game Driver package. + optional SettingProto game_driver_whitelist = 12; + // ANGLE - List of Apps that can check ANGLE rules + optional SettingProto angle_whitelist = 13; } optional Gpu gpu = 59; @@ -518,6 +522,8 @@ message GlobalSettingsProto { optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Packages that are whitelisted for ignoring location settings (during emergencies) + optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Location location = 69; @@ -535,6 +541,16 @@ message GlobalSettingsProto { // Whether automatic battery saver mode is controlled via percentage, // {@link #DYNAMIC_POWER_SAVINGS_ENABLED} or disabled. optional SettingProto automatic_power_saver_mode = 4 [ (android.privacy).dest = DEST_AUTOMATIC]; + // If 1, battery saver (low_power_mode) will be re-activated after the device is + // unplugged from a charger or rebooted. + optional SettingProto sticky_enabled = 5; + // Whether sticky battery saver should be deactivated once the battery level has reached the + // threshold specified by sticky_disable_level. + optional SettingProto sticky_auto_disable_enabled = 6; + // When a device is unplugged from a changer (or is rebooted), do not re-activate battery + // saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or + // above this threshold. + optional SettingProto sticky_auto_disable_level = 7; } optional LowPowerMode low_power_mode = 70; @@ -731,8 +747,7 @@ message GlobalSettingsProto { // Defines global runtime overrides to window policy. optional SettingProto policy_control = 92; optional SettingProto power_manager_constants = 93; - // If true, out-of-the-box execution for priv apps is enabled. - optional SettingProto priv_app_oob_enabled = 94 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 94; // Used to be priv_app_oob_enabled message PrepaidSetup { option (android.msg_privacy).dest = DEST_EXPLICIT; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 4bfd4d236abb..aaf6c63b2978 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -528,8 +528,11 @@ message SecureSettingsProto { optional SettingProto skip_gesture_enabled = 74 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 76; + // Next tag = 78; } diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index b70bb677d7a0..21717d886266 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -41,7 +41,7 @@ enum RadioTech { RADIO_TECHNOLOGY_UMTS = 3; RADIO_TECHNOLOGY_IS95A = 4; RADIO_TECHNOLOGY_IS95B = 5; - RADIO_TECHNOLOGY_1xRTT = 6; + RADIO_TECHNOLOGY_1XRTT = 6; RADIO_TECHNOLOGY_EVDO_0 = 7; RADIO_TECHNOLOGY_EVDO_A = 8; RADIO_TECHNOLOGY_HSDPA = 9; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 7f3ea7a249ba..188769d930d1 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -31,6 +31,7 @@ import "frameworks/base/core/proto/android/os/persistablebundle.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; +// Next tag: 21 message JobSchedulerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -139,9 +140,13 @@ message JobSchedulerServiceDumpProto { // The current limit on the number of concurrent JobServiceContext entries // we want to keep actively running a job. optional int32 max_active_jobs = 13; + + // Dump from JobConcurrencyManager. + optional JobConcurrencyManagerProto concurrency_manager = 20; } // A com.android.server.job.JobSchedulerService.Constants object. +// Next tag: 29 message ConstantsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -221,6 +226,8 @@ message ConstantsProto { optional bool use_heartbeats = 23; message TimeController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't // ready now. optional bool skip_not_ready_jobs = 1; @@ -228,6 +235,8 @@ message ConstantsProto { optional TimeController time_controller = 25; message QuotaController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; // How much time the package should have before transitioning from out-of-quota to in-quota. @@ -251,10 +260,57 @@ message ConstantsProto { optional int64 rare_window_size_ms = 6; // The maximum amount of time an app can have its jobs running within a 24 hour window. optional int64 max_execution_time_ms = 7; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_active = 8; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_working = 9; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_frequent = 10; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_rare = 11; + // The maximum number of jobs that should be allowed to run in the past + // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + optional int32 max_job_count_per_allowed_time = 12; } optional QuotaController quota_controller = 24; - // Next tag: 26 + // Max number of jobs, when screen is ON. + optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_on = 26; + + // Max number of jobs, when screen is OFF. + optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_off = 27; + + // In this time after screen turns on, we increase job concurrency. + optional int32 screen_off_job_concurrency_increase_delay_ms = 28; +} + +// Next tag: 4 +message MaxJobCountsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Total number of jobs to run simultaneously. + optional int32 total_jobs = 1; + + // Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. + optional int32 max_bg = 2; + + // We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any + // pending, rather than always running the TOTAL number of FG jobs. + optional int32 min_bg = 3; +} + +// Next tag: 5 +message MaxJobCountsPerMemoryTrimLevelProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional MaxJobCountsProto normal = 1; + optional MaxJobCountsProto moderate = 2; + optional MaxJobCountsProto low = 3; + optional MaxJobCountsProto critical = 4; } message StateControllerProto { @@ -788,3 +844,46 @@ message JobStatusDumpProto { // Next tag: 28 } + +// Dump from com.android.server.job.JobConcurrencyManager. +// Next tag: 7 +message JobConcurrencyManagerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Whether the device is interactive (== screen on) now or not. + optional bool current_interactive = 1; + // Similar to current_interactive, screen on or not, but it takes into account the off timeout. + optional bool effective_interactive = 2; + // How many milliseconds have passed since the last screen on. (i.e. 1000 == 1 sec ago) + optional int64 time_since_last_screen_on_ms = 3; + // How many milliseconds have passed since the last screen off. (i.e. 1000 == 1 sec ago) + optional int64 time_since_last_screen_off_ms = 4; + // Current max number of jobs. + optional JobCountTrackerProto job_count_tracker = 5; + // Current memory trim level. + optional int32 memory_trim_level = 6; +} + +// Dump from com.android.server.job.JobConcurrencyManager.JobCountTracker. +// Next tag: 8 +message JobCountTrackerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Number of total jos that can run simultaneously. + optional int32 config_num_max_total_jobs = 1; + // Number of background jos that can run simultaneously. + optional int32 config_num_max_bg_jobs = 2; + // Out of total jobs, this many background jobs should be guaranteed to be executed, even if + // there are the config_num_max_total_jobs count of foreground jobs pending. + optional int32 config_num_min_bg_jobs = 3; + + // Number of running foreground jobs. + optional int32 num_running_fg_jobs = 4; + // Number of running background jobs. + optional int32 num_running_bg_jobs = 5; + + // Number of pending foreground jobs. + optional int32 num_pending_fg_jobs = 6; + // Number of pending background jobs. + optional int32 num_pending_bg_jobs = 7; +} diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index cee556a7b67f..af0a942dea2f 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -350,4 +350,12 @@ message BatterySaverStateMachineProto { // The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. This is a cached value, so it could // be slightly different from what's in GlobalSettingsProto.LowPowerMode. optional int32 setting_battery_saver_trigger_threshold = 11; + + // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED. This is a cached value, so + // it could be slightly different from what's in GlobalSettingsProto.LowPowerMode. + optional bool setting_battery_saver_sticky_auto_disable_enabled = 12; + + // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL. This is a cached value, so it + // could be slightly different from what's in GlobalSettingsProto.LowPowerMode. + optional int32 setting_battery_saver_sticky_auto_disable_threshold = 13; } diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto index 528c1a4134f1..050ec7a0a95f 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -88,6 +88,11 @@ message IntervalStatsProto { // If class field is an Activity, instance_id is a unique id of the // Activity object. optional int32 instance_id = 14; + // task_root_package_index contains the index + 1 of the task root package name in the string + // pool + optional int32 task_root_package_index = 15; + // task_root_class_index contains the index + 1 of the task root class name in the string pool + optional int32 task_root_class_index = 16; } // The following fields contain supplemental data used to build IntervalStats, such as a string diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index f7dcee282a57..367c54086ade 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -228,6 +228,15 @@ message UsbPortProto { repeated Mode supported_modes = 2; } +/* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */ +enum ContaminantPresenceStatus { + CONTAMINANT_STATUS_UNKNOWN = 0; + CONTAMINANT_STATUS_NOT_SUPPORTED = 1; + CONTAMINANT_STATUS_DISABLED = 2; + CONTAMINANT_STATUS_NOT_DETECTED = 3; + CONTAMINANT_STATUS_DETECTED = 4; +} + message UsbPortStatusProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -250,6 +259,7 @@ message UsbPortStatusProto { optional PowerRole power_role = 3; optional DataRole data_role = 4; repeated UsbPortStatusRoleCombinationProto role_combinations = 5; + optional ContaminantPresenceStatus contaminant_presence_status = 6; } message UsbPortStatusRoleCombinationProto { diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 82460ec4ed8b..a8e64c6d8324 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -92,8 +92,7 @@ enum EventId { SET_UNINSTALL_BLOCKED = 67; SET_PACKAGES_SUSPENDED = 68; ON_LOCK_TASK_MODE_ENTERING = 69; - ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70; - REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71; + SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70; GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72; INSTALL_SYSTEM_UPDATE = 73; INSTALL_SYSTEM_UPDATE_ERROR = 74; diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto index fb4d5bdd1a7f..808c5143fe8e 100644 --- a/core/proto/android/view/remote_animation_target.proto +++ b/core/proto/android/view/remote_animation_target.proto @@ -42,4 +42,6 @@ message RemoteAnimationTargetProto { optional .android.graphics.PointProto position = 8; optional .android.graphics.RectProto source_container_bounds = 9; optional .android.app.WindowConfigurationProto window_configuration = 10; + optional .android.view.SurfaceControlProto start_leash = 11; + optional .android.graphics.RectProto start_bounds = 12; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ea0c8e250fe2..25baa921e8c9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -43,7 +43,7 @@ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" /> - <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" /> + <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" /> @@ -487,6 +487,7 @@ <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" /> <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" /> <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" /> + <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" /> <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" /> <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" /> <protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" /> @@ -1653,6 +1654,11 @@ <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE" android:protectionLevel="signature|privileged" /> + <!-- #SystemApi @hide Allows privileged system APK to update Wifi usability stats and score. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE" + android:protectionLevel="signature|privileged" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> @@ -1748,6 +1754,10 @@ <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" android:protectionLevel="signature" /> + <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. --> + <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" + android:protectionLevel="signature" /> + <!-- ================================== --> <!-- Permissions for accessing accounts --> <!-- ================================== --> @@ -2109,7 +2119,7 @@ <!-- ================================== --> <eat-comment /> - <!-- @SystemApi Allows an application to write to internal media storage + <!-- @SystemApi @TestApi Allows an application to write to internal media storage @hide --> <permission android:name="android.permission.WRITE_MEDIA_STORAGE" android:protectionLevel="signature|privileged" /> @@ -3319,7 +3329,7 @@ <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to clear user data. + <!-- @SystemApi @TestApi Allows an application to clear user data. <p>Not for use by third-party applications @hide --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 8ef264a84efb..de6468dbb72a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -950,6 +950,17 @@ <p>The default value of this attribute is <code>false</code>. --> <attr name="allowEmbedded" format="boolean" /> + <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on + top of the lock screen whenever the lockscreen is up and this activity has another + activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That + is, this activity is only visible on the lock screen if there is another activity with + the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the + lock screen. A use case for this is permission dialogs, that should only be visible on + the lock screen if their requesting activity is also visible. + + <p>The default value of this attribute is <code>false</code>. --> + <attr name="inheritShowWhenLocked" format="boolean" /> + <!-- Descriptive text for the associated data. --> <attr name="description" format="reference" /> @@ -2415,18 +2426,6 @@ <attr name="showForAllUsers" /> <attr name="showWhenLocked" /> - <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on - top of the lock screen whenever the lockscreen is up and this activity has another - activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That - is, this activity is only visible on the lock screen if there is another activity with - the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the - lock screen. A use case for this is permission dialogs, that should only be visible on - the lock screen if their requesting activity is also visible. - - The default value of this attribute is <code>false</code>. --> - <attr name="inheritShowWhenLocked" format="boolean" /> - - <attr name="inheritShowWhenLocked" /> <attr name="turnScreenOn" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 16d1d1a9b90a..c05795de4751 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -126,6 +126,9 @@ be sent during a change to the audio output device. --> <bool name="config_sendAudioBecomingNoisy">true</bool> + <!-- Whether Hearing Aid profile is supported --> + <bool name="config_hearing_aid_profile_supported">true</bool> + <!-- Flag to disable all transition animations --> <bool name="config_disableTransitionAnimation">false</bool> @@ -530,6 +533,9 @@ <!-- Boolean indicating whether the wifi chipset has dual frequency band support --> <bool translatable="false" name="config_wifi_dual_band_support">false</bool> + <!-- Maximum number of concurrent WiFi interfaces in AP mode --> + <integer translatable="false" name="config_wifi_max_ap_interfaces">1</integer> + <!-- Boolean indicating whether the wifi chipset requires the softap band be --> <!-- converted from 5GHz to ANY due to hardware restrictions --> <bool translatable="false" name="config_wifi_convert_apband_5ghz_to_any">false</bool> @@ -951,7 +957,7 @@ <!-- Default mode to control how Night display is automatically activated. One of the following values (see ColorDisplayController.java): 0 - AUTO_MODE_DISABLED - 1 - AUTO_MODE_CUSTOM + 1 - AUTO_MODE_CUSTOM_TIME 2 - AUTO_MODE_TWILIGHT --> <integer name="config_defaultNightDisplayAutoMode">0</integer> @@ -2205,6 +2211,10 @@ has expired, then assume the device is receiving insufficient current to charge effectively and terminate the dream. Use -1 to disable this safety feature. --> <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> + <!-- Limit of how long the device can remain unlocked due to attention checking. --> + <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes --> + <!-- How long we should wait until we give up on receiving an attention API callback. --> + <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds --> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead @@ -3723,9 +3733,6 @@ <!-- Whether or not the "SMS app service" feature is enabled --> <bool name="config_useSmsAppService">true</bool> - <!-- Component name for default assistant on this device --> - <string name="config_defaultAssistantComponentName">#+UNSET</string> - <!-- Class name for the InputEvent compatibility processor override. Empty string means use the default compatibility processor (android.view.InputEventCompatProcessor). --> @@ -3756,4 +3763,7 @@ <!-- Whether cbrs is supported on the device or not --> <bool translatable="false" name="config_cbrs_supported">false</bool> + + <!-- Whether or not aware is enabled by default --> + <bool name="config_awareSettingAvailable">false</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 777886a9911c..ec1bac1a41d6 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2944,6 +2944,8 @@ </public-group> <public-group type="style" first-id="0x010302e2"> + <!-- @hide @SystemApi --> + <public name="Theme.DeviceDefault.DocumentsUI" /> </public-group> <public-group type="id" first-id="0x01020046"> @@ -2986,7 +2988,7 @@ </public-group> <public-group type="array" first-id="0x01070006"> - <!-- @hide @SystemApi --> + <!-- @hide @TestApi @SystemApi --> <public name="config_defaultRoleHolders" /> </public-group> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f2b4b9ccfce8..a761baf95b0d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3387,6 +3387,9 @@ <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. --> <string name="wifi_no_internet_detailed">Tap for options</string> + <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] --> + <string name="captive_portal_logged_in_detailed">Connected</string> + <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notifications's title. [CHAR_LIMIT=NONE] --> @@ -3573,6 +3576,15 @@ <string name="adb_active_notification_message">Tap to turn off USB debugging</string> <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string> + <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string> + <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_detected_message">USB port is automatically disabled. Tap to learn more.</string> + <!-- Title of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_not_detected_title">Safe to use USB port</string> + <!-- Message of notification shown when contaminant is no longer detected on the USB port. [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_not_detected_message">Phone no longer detects liquid or debris.</string> + <!-- Title of notification shown to indicate that bug report is being collected. --> <string name="taking_remote_bugreport_notification_title">Taking bug report\u2026</string> <!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 751721860441..f79e22d1f94e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -437,6 +437,7 @@ <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" /> <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" /> <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" /> + <java-symbol type="bool" name="config_hearing_aid_profile_supported" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_drawLockTimeoutMillis" /> <java-symbol type="integer" name="config_doublePressOnPowerBehavior" /> @@ -694,6 +695,7 @@ <java-symbol type="string" name="capability_title_canControlMagnification" /> <java-symbol type="string" name="capability_desc_canPerformGestures" /> <java-symbol type="string" name="capability_title_canPerformGestures" /> + <java-symbol type="string" name="captive_portal_logged_in_detailed" /> <java-symbol type="string" name="cfTemplateForwarded" /> <java-symbol type="string" name="cfTemplateForwardedTime" /> <java-symbol type="string" name="cfTemplateNotForwarded" /> @@ -1879,6 +1881,7 @@ <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" /> <java-symbol type="bool" name="config_wifi_background_scan_support" /> <java-symbol type="bool" name="config_wifi_dual_band_support" /> + <java-symbol type="integer" name="config_wifi_max_ap_interfaces" /> <java-symbol type="bool" name="config_wifi_convert_apband_5ghz_to_any" /> <java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" /> <java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" /> @@ -2103,6 +2106,10 @@ <java-symbol type="string" name="usb_supplying_notification_title" /> <java-symbol type="string" name="usb_unsupported_audio_accessory_title" /> <java-symbol type="string" name="usb_unsupported_audio_accessory_message" /> + <java-symbol type="string" name="usb_contaminant_detected_title" /> + <java-symbol type="string" name="usb_contaminant_detected_message" /> + <java-symbol type="string" name="usb_contaminant_not_detected_title" /> + <java-symbol type="string" name="usb_contaminant_not_detected_message" /> <java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" /> <java-symbol type="string" name="vpn_text" /> <java-symbol type="string" name="vpn_text_long" /> @@ -3521,8 +3528,6 @@ <java-symbol type="bool" name="config_useSmsAppService" /> - <java-symbol type="string" name="config_defaultAssistantComponentName" /> - <java-symbol type="id" name="transition_overlay_view_tag" /> <java-symbol type="dimen" name="rounded_corner_radius" /> @@ -3547,4 +3552,10 @@ <!-- For CBRS --> <java-symbol type="bool" name="config_cbrs_supported" /> + + <java-symbol type="bool" name="config_awareSettingAvailable" /> + + <!-- For Attention Service --> + <java-symbol type="integer" name="config_attentionMaximumExtension" /> + <java-symbol type="integer" name="config_attentionApiTimeout" /> </resources> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 75a727bdcfdf..160350878349 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1711,4 +1711,6 @@ easier. <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item> </style> + <!-- @hide DeviceDefault theme for the DocumentsUI app. --> + <style name="Theme.DeviceDefault.DocumentsUI" parent="Theme.DeviceDefault.DayNight" /> </resources> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java index d2bd1e172ee2..11eb158317e9 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java @@ -18,13 +18,13 @@ package android.hardware.radio.tests.functional; import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.after; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.testng.Assert.assertThrows; import android.Manifest; @@ -119,10 +119,9 @@ public class RadioTunerTest { } private void resetCallback() { - verify(mCallback, atLeast(0)).onMetadataChanged(any()); - verify(mCallback, atLeast(0)).onProgramInfoChanged(any()); - verify(mCallback, atLeast(0)).onProgramListChanged(); - verifyNoMoreInteractions(mCallback); + verify(mCallback, never()).onError(anyInt()); + verify(mCallback, never()).onTuneFailed(anyInt(), any()); + verify(mCallback, never()).onControlChanged(anyBoolean()); Mockito.reset(mCallback); } diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 0fc3bd224fbf..8e8b07a9074b 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -49,6 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ telephony-common \ + testables \ org.apache.http.legacy \ android.test.base \ android.test.mock \ diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index 8d42c74be7b0..5731daa0b2a9 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -20,12 +20,23 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality; +import static android.app.admin.PasswordMetrics.getActualRequiredQuality; +import static android.app.admin.PasswordMetrics.getMinimumMetrics; +import static android.app.admin.PasswordMetrics.getTargetQualityMetrics; +import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; -import android.app.admin.PasswordMetrics.PasswordComplexityBucket; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -109,7 +120,7 @@ public class PasswordMetricsTest { assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, PasswordMetrics.computeForPassword("1").quality); // contains a long sequence so isn't complex - assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, + assertEquals(PASSWORD_QUALITY_NUMERIC, PasswordMetrics.computeForPassword("1234").quality); assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, PasswordMetrics.computeForPassword("").quality); @@ -145,7 +156,7 @@ public class PasswordMetricsTest { new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5)); assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4)); + new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4)); metrics0 = PasswordMetrics.computeForPassword("1234abcd,./"); metrics1 = PasswordMetrics.computeForPassword("1234abcd,./"); @@ -176,9 +187,9 @@ public class PasswordMetricsTest { @Test public void testConstructQuality() { PasswordMetrics expected = new PasswordMetrics(); - expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + expected.quality = PASSWORD_QUALITY_COMPLEX; - PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX); assertEquals(expected, actual); } @@ -256,42 +267,178 @@ public class PasswordMetricsTest { } @Test - public void testComplexityLevelToBucket_none() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_NONE).getMetrics(); + public void testSanitizeComplexityLevel_none() { + assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE)); - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity()); - } } @Test - public void testComplexityLevelToBucket_low() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_LOW).getMetrics(); + public void testSanitizeComplexityLevel_low() { + assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity()); - } + @Test + public void testSanitizeComplexityLevel_medium() { + assertEquals( + PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM)); } @Test - public void testComplexityLevelToBucket_medium() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_MEDIUM).getMetrics(); + public void testSanitizeComplexityLevel_high() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity()); - } + @Test + public void testSanitizeComplexityLevel_invalid() { + assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1)); } @Test - public void testComplexityLevelToBucket_high() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_HIGH).getMetrics(); + public void testComplexityLevelToMinQuality_none() { + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity()); - } + @Test + public void testComplexityLevelToMinQuality_low() { + assertEquals(PASSWORD_QUALITY_SOMETHING, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW)); + } + + @Test + public void testComplexityLevelToMinQuality_medium() { + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM)); + } + + @Test + public void testComplexityLevelToMinQuality_high() { + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH)); + } + + @Test + public void testComplexityLevelToMinQuality_invalid() { + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1)); + } + + @Test + public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() { + PasswordMetrics metrics = + getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC); + + assertTrue(metrics.isDefault()); + } + + @Test + public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() { + PasswordMetrics metrics = + getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_highComplexityNumericComplex() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 8, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC); + + assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetActualRequiredQuality_nonComplex() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_NUMERIC_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNone() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNumeric() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ true, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresLetters() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ true); + + assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ true, + /* requiresLettersOrSymbols= */ true); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual); + } + + @Test + public void testGetMinimumMetrics_userInputStricter() { + PasswordMetrics metrics = getMinimumMetrics( + PASSWORD_COMPLEXITY_HIGH, + PASSWORD_QUALITY_ALPHANUMERIC, + PASSWORD_QUALITY_NUMERIC, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality); + assertEquals(/* expected= */ 6, metrics.length); + } + + @Test + public void testGetMinimumMetrics_actualRequiredQualityStricter() { + PasswordMetrics metrics = getMinimumMetrics( + PASSWORD_COMPLEXITY_HIGH, + PASSWORD_QUALITY_UNSPECIFIED, + PASSWORD_QUALITY_NUMERIC, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 8, metrics.length); } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 8604b0c48476..bdf3aa2563db 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -38,7 +38,6 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -501,7 +500,7 @@ public class TransactionParcelTests { } @Override - public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException { + public void updateHttpProxy() throws RemoteException { } @Override diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java index 3d7aab001227..ad9814bd01b1 100644 --- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java @@ -48,21 +48,11 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate } /** - * Detect when the org.apache.http.legacy is not on the bootclasspath. - * - * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and - * succeed otherwise. This allows a developer to ensure that the tests are being - */ - @Test - public void detectWhenOAHLisOnBCP() { - Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL()); - } - - /** * Detect when the android.test.base is not on the bootclasspath. * * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and - * succeed otherwise. This allows a developer to ensure that the tests are being + * succeed otherwise. This allows a developer to ensure that the tests are being run in the + * correct environment. */ @Test public void detectWhenATBisOnBCP() { @@ -85,9 +75,7 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate if (!PackageBackwardCompatibility.bootClassPathContainsATB()) { expected.add(ANDROID_TEST_BASE); } - if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) { - expected.add(ORG_APACHE_HTTP_LEGACY); - } + expected.add(ORG_APACHE_HTTP_LEGACY); PackageBuilder after = builder() .targetSdkVersion(Build.VERSION_CODES.O) @@ -98,30 +86,6 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate /** * Ensures that the {@link PackageBackwardCompatibility} uses - * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest} - * when necessary. - * - * <p>More comprehensive tests for that class can be found in - * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}. - */ - @Test - public void org_apache_http_legacy_in_usesLibraries() { - Assume.assumeTrue("Test requires that " - + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath", - PackageBackwardCompatibility.bootClassPathContainsOAHL()); - - PackageBuilder before = builder() - .requiredLibraries(ORG_APACHE_HTTP_LEGACY); - - // org.apache.http.legacy should be removed from the libraries because it is provided - // on the bootclasspath and providing both increases start up cost unnecessarily. - PackageBuilder after = builder(); - - checkBackwardsCompatibility(before, after); - } - - /** - * Ensures that the {@link PackageBackwardCompatibility} uses * {@link RemoveUnnecessaryAndroidTestBaseLibrary} * when necessary. * diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index c5454a649e73..300394d426e4 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -527,12 +527,16 @@ public class PackageParserTest { R.raw.com_android_tzdata); PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false); assertEquals("com.google.android.tzdata", pi.packageName); + assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertEquals(1, pi.getLongVersionCode()); + assertEquals(1, pi.applicationInfo.longVersionCode); assertNull(pi.signingInfo); pi = PackageParser.generatePackageInfoFromApex(apexFile, true); assertEquals("com.google.android.tzdata", pi.packageName); + assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertEquals(1, pi.getLongVersionCode()); + assertEquals(1, pi.applicationInfo.longVersionCode); assertNotNull(pi.signingInfo); assertTrue(pi.signingInfo.getApkContentsSigners().length > 0); } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 87ad3d1067b2..bd7f8527fc6f 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -100,6 +100,7 @@ public class SettingsBackupTest { Settings.Global.ACTIVITY_MANAGER_CONSTANTS, Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, + Settings.Global.ADB_ALLOWED_CONNECTION_TIME, Settings.Global.ADB_ENABLED, Settings.Global.ADD_USERS_WHEN_LOCKED, Settings.Global.AIRPLANE_MODE_ON, @@ -120,6 +121,7 @@ public class SettingsBackupTest { Settings.Global.APP_IDLE_CONSTANTS, Settings.Global.APP_OPS_CONSTANTS, Settings.Global.APP_STANDBY_ENABLED, + Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, Settings.Global.ASSISTED_GPS_ENABLED, Settings.Global.AUDIO_SAFE_VOLUME_STATE, Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, @@ -129,6 +131,7 @@ public class SettingsBackupTest { Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, Settings.Global.AUTOMATIC_POWER_SAVER_MODE, Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, @@ -296,6 +299,7 @@ public class SettingsBackupTest { Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST, + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS, Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, Settings.Global.LOCATION_GLOBAL_KILL_SWITCH, @@ -386,12 +390,9 @@ public class SettingsBackupTest { Settings.Global.POLICY_CONTROL, Settings.Global.POWER_MANAGER_CONSTANTS, Settings.Global.PREFERRED_NETWORK_MODE, - Settings.Global.PRIV_APP_OOB_ENABLED, - Settings.Global.PRIV_APP_OOB_LIST, Settings.Global.PRIVATE_DNS_DEFAULT_MODE, - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, + Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED, Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS, Settings.Global.RADIO_BLUETOOTH, @@ -481,10 +482,12 @@ public class SettingsBackupTest { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, Settings.Global.GUP_DEV_ALL_APPS, Settings.Global.GUP_DEV_OPT_IN_APPS, Settings.Global.GUP_DEV_OPT_OUT_APPS, Settings.Global.GUP_BLACKLIST, + Settings.Global.GAME_DRIVER_WHITELIST, Settings.Global.GPU_DEBUG_LAYER_APP, Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT, @@ -562,7 +565,9 @@ public class SettingsBackupTest { Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS, Settings.Global.ENABLE_RADIO_BUG_DETECTION, Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD, - Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD); + Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD, + Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT, + Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT); private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java new file mode 100644 index 000000000000..77e8d608810f --- /dev/null +++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.NoSuchElementException; + +/** + * Internal tests for {@link LongArrayQueue}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LongArrayQueueTest { + + private LongArrayQueue mQueueUnderTest; + + @Before + public void setUp() { + mQueueUnderTest = new LongArrayQueue(); + } + + @Test + public void removeFirstOnEmptyQueue() { + try { + mQueueUnderTest.removeFirst(); + fail("removeFirst() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + mQueueUnderTest.addLast(5); + mQueueUnderTest.removeFirst(); + try { + mQueueUnderTest.removeFirst(); + fail("removeFirst() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void addLastRemoveFirstFifo() { + mQueueUnderTest.addLast(1); + assertEquals(1, mQueueUnderTest.removeFirst()); + int n = 890; + int removes = 0; + for (int i = 0; i < n; i++) { + mQueueUnderTest.addLast(i); + if ((i % 3) == 0) { + assertEquals(removes++, mQueueUnderTest.removeFirst()); + } + } + while (removes < n) { + assertEquals(removes++, mQueueUnderTest.removeFirst()); + } + } + + @Test + public void peekFirstOnEmptyQueue() { + try { + mQueueUnderTest.peekFirst(); + fail("peekFirst() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + mQueueUnderTest.addLast(5); + mQueueUnderTest.removeFirst(); + try { + mQueueUnderTest.peekFirst(); + fail("peekFirst() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void peekFirstChanges() { + mQueueUnderTest.addLast(1); + assertEquals(1, mQueueUnderTest.peekFirst()); + mQueueUnderTest.addLast(2); + mQueueUnderTest.addLast(3); + mQueueUnderTest.addLast(4); + // addLast() has no effect on peekFirst(). + assertEquals(1, mQueueUnderTest.peekFirst()); + mQueueUnderTest.removeFirst(); + mQueueUnderTest.removeFirst(); + assertEquals(3, mQueueUnderTest.peekFirst()); + } + + @Test + public void peekLastOnEmptyQueue() { + try { + mQueueUnderTest.peekLast(); + fail("peekLast() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + mQueueUnderTest.addLast(5); + mQueueUnderTest.removeFirst(); + try { + mQueueUnderTest.peekLast(); + fail("peekLast() succeeded on an empty queue!"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void peekLastChanges() { + mQueueUnderTest.addLast(1); + assertEquals(1, mQueueUnderTest.peekLast()); + mQueueUnderTest.addLast(2); + mQueueUnderTest.addLast(3); + mQueueUnderTest.addLast(4); + assertEquals(4, mQueueUnderTest.peekLast()); + mQueueUnderTest.removeFirst(); + mQueueUnderTest.removeFirst(); + // removeFirst() has no effect on peekLast(). + assertEquals(4, mQueueUnderTest.peekLast()); + } + + @Test + public void peekFirstVsPeekLast() { + mQueueUnderTest.addLast(2); + assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast()); + mQueueUnderTest.addLast(3); + assertNotEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast()); + mQueueUnderTest.removeFirst(); + assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast()); + } + + @Test + public void peekFirstVsRemoveFirst() { + int n = 25; + for (int i = 0; i < n; i++) { + mQueueUnderTest.addLast(i + 1); + } + for (int i = 0; i < n; i++) { + long peekVal = mQueueUnderTest.peekFirst(); + assertEquals(peekVal, mQueueUnderTest.removeFirst()); + } + } + + @Test + public void sizeOfEmptyQueue() { + assertEquals(0, mQueueUnderTest.size()); + mQueueUnderTest = new LongArrayQueue(1000); + // capacity doesn't affect size. + assertEquals(0, mQueueUnderTest.size()); + } + + @Test + public void sizeAfterOperations() { + final int added = 1200; + for (int i = 0; i < added; i++) { + mQueueUnderTest.addLast(i); + } + // each add increments the size by 1. + assertEquals(added, mQueueUnderTest.size()); + mQueueUnderTest.peekLast(); + mQueueUnderTest.peekFirst(); + // peeks don't change the size. + assertEquals(added, mQueueUnderTest.size()); + final int removed = 345; + for (int i = 0; i < removed; i++) { + mQueueUnderTest.removeFirst(); + } + // each remove decrements the size by 1. + assertEquals(added - removed, mQueueUnderTest.size()); + mQueueUnderTest.clear(); + // clear reduces the size to 0. + assertEquals(0, mQueueUnderTest.size()); + } + + @Test + public void getInvalidPositions() { + try { + mQueueUnderTest.get(0); + fail("get(0) succeeded on an empty queue!"); + } catch (IndexOutOfBoundsException e) { + } + int n = 520; + for (int i = 0; i < 2 * n; i++) { + mQueueUnderTest.addLast(i + 1); + } + for (int i = 0; i < n; i++) { + mQueueUnderTest.removeFirst(); + } + try { + mQueueUnderTest.get(-3); + fail("get(-3) succeeded"); + } catch (IndexOutOfBoundsException e) { + } + assertEquals(n, mQueueUnderTest.size()); + try { + mQueueUnderTest.get(n); + fail("get(" + n + ") succeeded on a queue with " + n + " elements"); + } catch (IndexOutOfBoundsException e) { + } + try { + mQueueUnderTest.get(n + 3); + fail("get(" + (n + 3) + ") succeeded on a queue with " + n + " elements"); + } catch (IndexOutOfBoundsException e) { + } + } + + @Test + public void getValidPositions() { + int added = 423; + int removed = 212; + for (int i = 0; i < added; i++) { + mQueueUnderTest.addLast(i); + } + for (int i = 0; i < removed; i++) { + mQueueUnderTest.removeFirst(); + } + for (int i = 0; i < (added - removed); i++) { + assertEquals(removed + i, mQueueUnderTest.get(i)); + } + } +} diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 7f00ad9ed2f2..7cd3c44d9a4e 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -101,6 +101,7 @@ public class InsetsAnimationControlImplTest { @Test public void testChangeInsets() { mController.changeInsets(Insets.of(0, 30, 40, 0)); + mController.applyChangeInsets(new InsetsState()); assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets()); ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index d44745121a22..8f2109676dfb 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -16,15 +16,25 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; - +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets.Type; +import android.view.WindowManager.BadTokenException; +import android.view.WindowManager.LayoutParams; +import android.widget.TextView; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -37,8 +47,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class InsetsControllerTest { - private InsetsController mController = new InsetsController(mock(ViewRootImpl.class)); - + private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; @@ -47,6 +56,24 @@ public class InsetsControllerTest { mLeash = new SurfaceControl.Builder(mSession) .setName("testSurface") .build(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Context context = InstrumentationRegistry.getTargetContext(); + // cannot mock ViewRootImpl since it's final. + ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + try { + viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + } catch (BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mController = new InsetsController(viewRootImpl); + final Rect rect = new Rect(5, 5, 5, 5); + mController.calculateInsets( + false, + false, + new DisplayCutout( + Insets.of(10, 10, 10, 10), rect, rect, rect, rect), + rect, rect); + }); } @Test @@ -64,4 +91,39 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[0]); assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl()); } + + @Test + public void testAnimationEndState() { + final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash); + final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash); + final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash); + + InsetsSourceControl[] controls = new InsetsSourceControl[3]; + controls[0] = navBar; + controls[1] = topBar; + controls[2] = ime; + mController.onControlsChanged(controls); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mController.show(Type.all()); + // quickly jump to final state by cancelling it. + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.all()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.show(Type.ime()); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.ime()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + }); + } } diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index d57fa8f9f612..6a83c29b0943 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -20,6 +20,7 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.sideBars; import static android.view.WindowInsets.Type.topBar; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.graphics.Insets; @@ -73,4 +74,29 @@ public class WindowInsetsTest { assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar())); assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars())); } + + // TODO: Move this to CTS once API made public + @Test + public void visibility() { + Builder b = new WindowInsets.Builder(); + b.setInsets(sideBars(), Insets.of(0, 0, 0, 100)); + b.setInsets(ime(), Insets.of(0, 0, 0, 300)); + b.setVisible(sideBars(), true); + b.setVisible(ime(), true); + WindowInsets insets = b.build(); + assertTrue(insets.isVisible(sideBars())); + assertTrue(insets.isVisible(sideBars() | ime())); + assertFalse(insets.isVisible(sideBars() | topBar())); + } + + // TODO: Move this to CTS once API made public + @Test + public void consume_doesntChangeVisibility() { + Builder b = new WindowInsets.Builder(); + b.setInsets(ime(), Insets.of(0, 0, 0, 300)); + b.setVisible(ime(), true); + WindowInsets insets = b.build(); + insets = insets.consumeSystemWindowInsets(); + assertTrue(insets.isVisible(ime())); + } } diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java index 33bc59349230..2f17b32370f4 100644 --- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java +++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java @@ -34,26 +34,57 @@ public class AutofillIdTest { public void testNonVirtual() { final AutofillId id = new AutofillId(42); assertThat(id.getViewId()).isEqualTo(42); - assertThat(id.isVirtual()).isFalse(); - assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID); + assertThat(id.isNonVirtual()).isTrue(); + assertThat(id.isVirtualInt()).isFalse(); + assertThat(id.isVirtualLong()).isFalse(); + assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID); + assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID); final AutofillId clone = cloneThroughParcel(id); assertThat(clone.getViewId()).isEqualTo(42); - assertThat(clone.isVirtual()).isFalse(); - assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID); + assertThat(clone.isNonVirtual()).isTrue(); + assertThat(clone.isVirtualInt()).isFalse(); + assertThat(clone.isVirtualLong()).isFalse(); + assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID); + assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID); } @Test - public void testVirtual() { + public void testVirtual_int() { final AutofillId id = new AutofillId(42, 108); assertThat(id.getViewId()).isEqualTo(42); - assertThat(id.isVirtual()).isTrue(); - assertThat(id.getVirtualChildId()).isEqualTo(108); + assertThat(id.isVirtualInt()).isTrue(); + assertThat(id.isVirtualLong()).isFalse(); + assertThat(id.isNonVirtual()).isFalse(); + assertThat(id.getVirtualChildIntId()).isEqualTo(108); + assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID); final AutofillId clone = cloneThroughParcel(id); assertThat(clone.getViewId()).isEqualTo(42); - assertThat(clone.isVirtual()).isTrue(); - assertThat(clone.getVirtualChildId()).isEqualTo(108); + assertThat(clone.isVirtualLong()).isFalse(); + assertThat(clone.isVirtualInt()).isTrue(); + assertThat(clone.isNonVirtual()).isFalse(); + assertThat(clone.getVirtualChildIntId()).isEqualTo(108); + assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID); + } + + @Test + public void testVirtual_long() { + final AutofillId id = new AutofillId(new AutofillId(42), 4815162342L, 108); + assertThat(id.getViewId()).isEqualTo(42); + assertThat(id.isVirtualLong()).isTrue(); + assertThat(id.isVirtualInt()).isFalse(); + assertThat(id.isNonVirtual()).isFalse(); + assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID); + assertThat(id.getVirtualChildLongId()).isEqualTo(4815162342L); + + final AutofillId clone = cloneThroughParcel(id); + assertThat(clone.getViewId()).isEqualTo(42); + assertThat(clone.isVirtualLong()).isTrue(); + assertThat(clone.isVirtualInt()).isFalse(); + assertThat(clone.isNonVirtual()).isFalse(); + assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID); + assertThat(clone.getVirtualChildLongId()).isEqualTo(4815162342L); } @Test @@ -62,27 +93,33 @@ public class AutofillIdTest { final AutofillId id = new AutofillId(new AutofillId(42), 108); assertThat(id.getViewId()).isEqualTo(42); - assertThat(id.isVirtual()).isTrue(); - assertThat(id.getVirtualChildId()).isEqualTo(108); + assertThat(id.isVirtualInt()).isTrue(); + assertThat(id.getVirtualChildIntId()).isEqualTo(108); final AutofillId clone = cloneThroughParcel(id); assertThat(clone.getViewId()).isEqualTo(42); - assertThat(clone.isVirtual()).isTrue(); - assertThat(clone.getVirtualChildId()).isEqualTo(108); + assertThat(clone.isVirtualInt()).isTrue(); + assertThat(clone.getVirtualChildIntId()).isEqualTo(108); } @Test public void testVirtual_withSession() { - final AutofillId id = new AutofillId(new AutofillId(42), 108, 666); + final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666); assertThat(id.getViewId()).isEqualTo(42); - assertThat(id.isVirtual()).isTrue(); - assertThat(id.getVirtualChildId()).isEqualTo(108); + assertThat(id.isVirtualLong()).isTrue(); + assertThat(id.isVirtualInt()).isFalse(); + assertThat(id.isNonVirtual()).isFalse(); + assertThat(id.getVirtualChildLongId()).isEqualTo(108L); + assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID); assertThat(id.getSessionId()).isEqualTo(666); final AutofillId clone = cloneThroughParcel(id); assertThat(clone.getViewId()).isEqualTo(42); - assertThat(clone.isVirtual()).isTrue(); - assertThat(clone.getVirtualChildId()).isEqualTo(108); + assertThat(clone.isVirtualLong()).isTrue(); + assertThat(clone.isVirtualInt()).isFalse(); + assertThat(clone.isNonVirtual()).isFalse(); + assertThat(clone.getVirtualChildLongId()).isEqualTo(108L); + assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID); assertThat(clone.getSessionId()).isEqualTo(666); } @@ -118,13 +155,14 @@ public class AutofillIdTest { assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild); assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent); - final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108); + final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1L, 108); assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId); assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession); assertThat(virtualIdDifferentSession).isNotEqualTo(realId); assertThat(realId).isNotEqualTo(virtualIdDifferentSession); - final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108); + final AutofillId sameVirtualIdDifferentSession = + new AutofillId(new AutofillId(42), 1L, 108); assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession); assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession); assertThat(sameVirtualIdDifferentSession.hashCode()) diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java new file mode 100644 index 000000000000..312e0e0d8268 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.contentcapture; + +import static org.testng.Assert.assertThrows; + +import android.content.Context; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit test for {@link ContentCaptureManager}. + * + * <p>To run it: + * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureManagerTest} + */ +@RunWith(MockitoJUnitRunner.class) +public class ContentCaptureManagerTest { + + @Mock + private Context mMockContext; + + private ContentCaptureManager mManager; + + @Before + public void before() { + mManager = new ContentCaptureManager(mMockContext, null); + } + + @Test + public void testRemoveUserData_invalid() { + assertThrows(NullPointerException.class, () -> mManager.removeUserData(null)); + } +} diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index ff97aa1d3914..bfa6e062b08d 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -31,7 +31,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; /** - * Unit test for {@link ContentCaptureSessionTest}. + * Unit tests for {@link ContentCaptureSession}. * * <p>To run it: * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest} @@ -48,17 +48,18 @@ public class ContentCaptureSessionTest { @Test public void testNewAutofillId_invalid() { - assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42)); + assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L)); assertThrows(IllegalArgumentException.class, - () -> mSession1.newAutofillId(new AutofillId(42, 42), 42)); + () -> mSession1.newAutofillId(new AutofillId(42, 42), 42L)); } @Test public void testNewAutofillId_valid() { final AutofillId parentId = new AutofillId(42); - final AutofillId childId = mSession1.newAutofillId(parentId, 108); + final AutofillId childId = mSession1.newAutofillId(parentId, 108L); assertThat(childId.getViewId()).isEqualTo(42); - assertThat(childId.getVirtualChildId()).isEqualTo(108); + assertThat(childId.getVirtualChildLongId()).isEqualTo(108L); + assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID); assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt()); } @@ -66,8 +67,8 @@ public class ContentCaptureSessionTest { public void testNewAutofillId_differentSessions() { assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check final AutofillId parentId = new AutofillId(42); - final AutofillId childId1 = mSession1.newAutofillId(parentId, 108); - final AutofillId childId2 = mSession2.newAutofillId(parentId, 108); + final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L); + final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L); assertThat(childId1).isNotEqualTo(childId2); assertThat(childId2).isNotEqualTo(childId1); } @@ -91,9 +92,9 @@ public class ContentCaptureSessionTest { @Test public void testNewVirtualViewStructure() { final AutofillId parentId = new AutofillId(42); - final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108); + final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108L); assertThat(structure).isNotNull(); - final AutofillId childId = mSession1.newAutofillId(parentId, 108); + final AutofillId childId = mSession1.newAutofillId(parentId, 108L); assertThat(structure.getAutofillId()).isEqualTo(childId); } @@ -101,16 +102,16 @@ public class ContentCaptureSessionTest { public void testNotifyViewsDisappeared_invalid() { // Null parent assertThrows(NullPointerException.class, - () -> mSession1.notifyViewsDisappeared(null, new int[] {42})); + () -> mSession1.notifyViewsDisappeared(null, new long[] {42})); // Null child assertThrows(IllegalArgumentException.class, () -> mSession1.notifyViewsDisappeared(new AutofillId(42), null)); // Empty child assertThrows(IllegalArgumentException.class, - () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new int[] {})); + () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new long[] {})); // Virtual parent assertThrows(IllegalArgumentException.class, - () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new int[] {666})); + () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666})); } // Cannot use @Spy because we need to pass the session id on constructor diff --git a/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java new file mode 100644 index 000000000000..bebb2a89f480 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.contentcapture; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.net.Uri; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit test for {@link UserDataRemovalRequest}. + * + * <p>To run it: + * {@code atest FrameworksCoreTests:android.view.contentcapture.UserDataRemovalRequestTest} + */ +@RunWith(MockitoJUnitRunner.class) +public class UserDataRemovalRequestTest { + + @Mock + private final Uri mUri = Uri.parse("content://com.example/"); + + private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder(); + + @Test + public void testBuilder_addUri_invalid() { + assertThrows(NullPointerException.class, () -> mBuilder.addUri(null, false)); + } + + @Test + public void testBuilder_addUri_valid() { + assertThat(mBuilder.addUri(mUri, false)).isNotNull(); + assertThat(mBuilder.addUri(Uri.parse("content://com.example2"), true)).isNotNull(); + } + + @Test + public void testBuilder_addUriAfterForEverything() { + assertThat(mBuilder.forEverything()).isNotNull(); + assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false)); + } + + @Test + public void testBuilder_forEverythingAfterAddingUri() { + assertThat(mBuilder.addUri(mUri, false)).isNotNull(); + assertThrows(IllegalStateException.class, () -> mBuilder.forEverything()); + } + + @Test + public void testBuild_invalid() { + assertThrows(IllegalStateException.class, () -> mBuilder.build()); + } + + @Test + public void testBuild_valid() { + assertThat(new UserDataRemovalRequest.Builder().forEverything().build()) + .isNotNull(); + assertThat(new UserDataRemovalRequest.Builder().addUri(mUri, false).build()) + .isNotNull(); + } + + @Test + public void testNoMoreInteractionsAfterBuild() { + assertThat(mBuilder.forEverything().build()).isNotNull(); + + assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false)); + assertThrows(IllegalStateException.class, () -> mBuilder.forEverything()); + assertThrows(IllegalStateException.class, () -> mBuilder.build()); + + } +} diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java index eadde6280848..b84a098574c2 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java @@ -42,7 +42,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Locale; /** - * Unit test for {@link ViewNode}. + * Unit tests for {@link ViewNode}. * * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest} */ diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index 780e15ab885e..5022e305ecc2 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,8 +16,8 @@ package android.view.textclassifier; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; import static com.google.common.truth.Truth.assertThat; @@ -58,7 +58,7 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); + new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( @@ -81,7 +81,7 @@ public class ActionsSuggestionsHelperTest { .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_LOCAL) + new ConversationActions.Message.Builder(PERSON_USER_SELF) .setText("third") .build(); ConversationActions.Message fourthMessage = @@ -104,16 +104,16 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("first") .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("third") .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 7009fb2ea758..5e58f82038f1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -262,6 +262,9 @@ public class TextClassifierTest { assertEquals( context.getString(com.android.internal.R.string.translate), classification.getActions().get(0).getTitle()); + Intent intent = (Intent) classification.getExtras() + .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0); + assertEquals(Intent.ACTION_TRANSLATE, intent.getAction()); LocaleList.setDefault(originalLocales); } @@ -375,7 +378,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = @@ -404,7 +407,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java index b1b74160ecd5..73af56743b5f 100644 --- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java @@ -18,9 +18,10 @@ package android.view.textclassifier.logging; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE; import static com.google.common.truth.Truth.assertThat; @@ -71,7 +72,8 @@ public class TextClassifierEventTronLoggerTest { new TextClassifierEvent.Builder( TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, TextClassifierEvent.TYPE_SMART_ACTION) - .setEntityType(ConversationAction.TYPE_CALL_PHONE) + .setEntityTypes(ConversationAction.TYPE_CALL_PHONE) + .setScore(0.5f) .setEventTime(EVENT_TIME) .setEventContext(textClassificationContext) .build(); @@ -83,15 +85,18 @@ public class TextClassifierEventTronLoggerTest { LogMaker logMaker = captor.getValue(); assertThat(logMaker.getCategory()).isEqualTo( CONVERSATION_ACTIONS); - assertThat(logMaker.getType()).isEqualTo( + assertThat(logMaker.getSubtype()).isEqualTo( ACTION_TEXT_SELECTION_SMART_SHARE); - assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE)) + assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE)) .isEqualTo(ConversationAction.TYPE_CALL_PHONE); + assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE)) + .isWithin(0.00001f).of(0.5f); assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME)) .isEqualTo(EVENT_TIME); assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME); - assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE)) + assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE)) .isEqualTo(WIDGET_TYPE); + } @Test diff --git a/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java new file mode 100644 index 000000000000..b740eccfb794 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.statusbar; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class NotificationVisibilityTest { + + @Test + public void testNotificationLocation_sameValuesAsMetricsProto() throws Exception { + for (NotificationVisibility.NotificationLocation location : + NotificationVisibility.NotificationLocation.values()) { + Field locationField = MetricsEvent.class.getField(location.name()); + int metricsValue = locationField.getInt(null); + assertThat(metricsValue, is(location.toMetricsEventEnum())); + } + } +} diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java new file mode 100644 index 000000000000..1a81c2c9fd41 --- /dev/null +++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.test.filters; + +import android.os.Bundle; + +import com.android.test.filters.SelectTest; + +/** + * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests. + * + * <p>Use this filter when running FrameworksCoreTests as + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.server.wm.test.filters.CoreTestsFilter \ + * -e selectTest_verbose true \ + * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner + * </pre> + */ +public final class CoreTestsFilter extends SelectTest { + + private static final String[] SELECTED_CORE_TESTS = { + "android.app.servertransaction.", // all tests under the package. + "android.view.DisplayCutoutTest", + "android.view.InsetsControllerTest", + "android.view.InsetsSourceTest", + "android.view.InsetsSourceConsumerTest", + "android.view.InsetsStateTest", + }; + + public CoreTestsFilter(Bundle testArgs) { + super(addSelectTest(testArgs, SELECTED_CORE_TESTS)); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index 03cf3eb6a2b9..9913531cdf13 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -67,6 +68,7 @@ public class LockPatternUtilsTest { // TODO(b/63758238): stop spying the class under test mLockPatternUtils = spy(new LockPatternUtils(context)); when(mLockPatternUtils.getLockSettings()).thenReturn(ils); + doReturn(true).when(mLockPatternUtils).hasSecureLockScreen(); final UserInfo userInfo = Mockito.mock(UserInfo.class); when(userInfo.isDemo()).thenReturn(isDemoUser); diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index bfbdbc585e08..8636949943b7 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1711,20 +1711,22 @@ public final class Bitmap implements Parcelable { */ @Nullable public final ColorSpace getColorSpace() { - // A reconfigure can change the configuration and rgba16f is - // always linear scRGB at this time - if (getConfig() == Config.RGBA_F16) { - // Reset the color space for potential future reconfigurations - mColorSpace = null; - return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); - } - + checkRecycled("getColorSpace called on a recycled bitmap"); // Cache the color space retrieval since it can be fairly expensive if (mColorSpace == null) { - if (nativeIsSRGB(mNativePtr)) { + if (nativeIsConfigF16(mNativePtr)) { + // an F16 bitmaps is intended to always be linear extended, but due to + // inconsistencies in Bitmap.create() functions it is possible to have + // rendered into a bitmap in non-linear sRGB. + if (nativeIsSRGB(mNativePtr)) { + mColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB); + } else { + mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + } + } else if (nativeIsSRGB(mNativePtr)) { mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) { - mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + } else if (nativeIsSRGBLinear(mNativePtr)) { + mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB); } else { float[] xyz = new float[9]; float[] params = new float[7]; @@ -2127,6 +2129,7 @@ public final class Bitmap implements Parcelable { private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color); private static native int nativeRowBytes(long nativeBitmap); private static native int nativeConfig(long nativeBitmap); + private static native boolean nativeIsConfigF16(long nativeBitmap); private static native int nativeGetPixel(long nativeBitmap, int x, int y); private static native void nativeGetPixels(long nativeBitmap, int[] pixels, diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 4755d45cd434..c9e46942a51a 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1821,6 +1821,8 @@ public abstract class ColorSpace { * @param cct The correlated color temperature, in Kelvin * @return Corresponding XYZ values * @throws IllegalArgumentException If cct is invalid + * + * @hide */ @NonNull @Size(3) @@ -1851,6 +1853,8 @@ public abstract class ColorSpace { * @param srcWhitePoint The white point to adapt from * @param dstWhitePoint The white point to adapt to * @return A 3x3 matrix as a non-null array of 9 floats + * + * @hide */ @NonNull @Size(9) diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index e4020554786b..c4ddd50616e8 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -20,6 +20,7 @@ import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.Activity; import android.app.ActivityManager; import android.os.IBinder; @@ -667,6 +668,17 @@ public class HardwareRenderer { nSetContentDrawBounds(mNativeProxy, left, top, right, bottom); } + /** @hide */ + public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) { + nSetPictureCaptureCallback(mNativeProxy, callback); + } + + /** called by native */ + static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) { + Picture picture = new Picture(picturePtr); + callback.onPictureCaptured(picture); + } + /** * Interface used to receive callbacks when a frame is being drawn. * @@ -695,6 +707,17 @@ public class HardwareRenderer { void onFrameComplete(long frameNr); } + /** + * Interface for listening to picture captures + * @hide + */ + @TestApi + public interface PictureCapturedCallback { + /** @hide */ + @TestApi + void onPictureCaptured(Picture picture); + } + private static void validateAlpha(float alpha, String argumentName) { if (!(alpha >= 0.0f && alpha <= 1.0f)) { throw new IllegalArgumentException(argumentName + " must be a valid alpha, " @@ -998,6 +1021,9 @@ public class HardwareRenderer { private static native void nSetContentDrawBounds(long nativeProxy, int left, int top, int right, int bottom); + private static native void nSetPictureCaptureCallback(long nativeProxy, + PictureCapturedCallback callback); + private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback); private static native void nSetFrameCompleteCallback(long nativeProxy, diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index f6d801b3ba43..8d12cbffc793 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import java.io.InputStream; @@ -34,7 +35,8 @@ import java.io.OutputStream; */ public class Picture { private PictureCanvas mRecordingCanvas; - @UnsupportedAppUsage + // TODO: Figure out if this was a false-positive + @UnsupportedAppUsage(maxTargetSdk = 28) private long mNativePicture; private boolean mRequiresHwAcceleration; @@ -56,23 +58,43 @@ public class Picture { this(nativeConstructor(src != null ? src.mNativePicture : 0)); } - private Picture(long nativePicture) { + /** @hide */ + public Picture(long nativePicture) { if (nativePicture == 0) { - throw new RuntimeException(); + throw new IllegalArgumentException(); } mNativePicture = nativePicture; } + /** + * Immediately releases the backing data of the Picture. This object will no longer + * be usable after calling this, and any further calls on the Picture will throw an + * IllegalStateException. + * // TODO: Support? + * @hide + */ + public void close() { + if (mNativePicture != 0) { + nativeDestructor(mNativePicture); + mNativePicture = 0; + } + } + @Override protected void finalize() throws Throwable { try { - nativeDestructor(mNativePicture); - mNativePicture = 0; + close(); } finally { super.finalize(); } } + private void verifyValid() { + if (mNativePicture == 0) { + throw new IllegalStateException("Picture is destroyed"); + } + } + /** * To record a picture, call beginRecording() and then draw into the Canvas * that is returned. Nothing we appear on screen, but all of the draw @@ -81,7 +103,9 @@ public class Picture { * that was returned must no longer be used, and nothing should be drawn * into it. */ + @NonNull public Canvas beginRecording(int width, int height) { + verifyValid(); if (mRecordingCanvas != null) { throw new IllegalStateException("Picture already recording, must call #endRecording()"); } @@ -98,6 +122,7 @@ public class Picture { * or {@link Canvas#drawPicture(Picture)} is called. */ public void endRecording() { + verifyValid(); if (mRecordingCanvas != null) { mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; mRecordingCanvas = null; @@ -110,7 +135,8 @@ public class Picture { * does not reflect (per se) the content of the picture. */ public int getWidth() { - return nativeGetWidth(mNativePicture); + verifyValid(); + return nativeGetWidth(mNativePicture); } /** @@ -118,7 +144,8 @@ public class Picture { * does not reflect (per se) the content of the picture. */ public int getHeight() { - return nativeGetHeight(mNativePicture); + verifyValid(); + return nativeGetHeight(mNativePicture); } /** @@ -133,6 +160,7 @@ public class Picture { * false otherwise. */ public boolean requiresHardwareAcceleration() { + verifyValid(); return mRequiresHwAcceleration; } @@ -149,7 +177,8 @@ public class Picture { * * @param canvas The picture is drawn to this canvas */ - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { + verifyValid(); if (mRecordingCanvas != null) { endRecording(); } @@ -172,7 +201,7 @@ public class Picture { * raw or compressed pixels. */ @Deprecated - public static Picture createFromStream(InputStream stream) { + public static Picture createFromStream(@NonNull InputStream stream) { return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); } @@ -188,10 +217,11 @@ public class Picture { * Bitmap from which you can persist it as raw or compressed pixels. */ @Deprecated - public void writeToStream(OutputStream stream) { + public void writeToStream(@NonNull OutputStream stream) { + verifyValid(); // do explicit check before calling the native method if (stream == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("stream cannot be null"); } if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) { throw new RuntimeException(); diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 3b1d44b44ed4..09b18b771250 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -184,7 +184,7 @@ public final class RenderNode { * * @param name The name of the RenderNode, used for debugging purpose. May be null. */ - public RenderNode(String name) { + public RenderNode(@Nullable String name) { this(name, null); } diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp new file mode 100644 index 000000000000..1d06348fb02f --- /dev/null +++ b/graphics/proto/Android.bp @@ -0,0 +1,11 @@ +java_library_static { + name: "game-driver-protos", + host_supported: true, + proto: { + type: "lite", + }, + srcs: ["game_driver.proto"], + no_framework_libs: true, + jarjar_rules: "jarjar-rules.txt", + sdk_version: "28", +} diff --git a/graphics/proto/game_driver.proto b/graphics/proto/game_driver.proto new file mode 100644 index 000000000000..fd7ffccac24c --- /dev/null +++ b/graphics/proto/game_driver.proto @@ -0,0 +1,31 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.gamedriver; + +option java_package = "android.gamedriver"; +option java_outer_classname = "GameDriverProto"; + +message Blacklist { + optional int64 version_code = 1; + repeated string package_names = 2; +} + +message Blacklists { + repeated Blacklist blacklists = 1; +} diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt new file mode 100644 index 000000000000..4e4063706352 --- /dev/null +++ b/graphics/proto/jarjar-rules.txt @@ -0,0 +1 @@ +rule com.google.protobuf.** com.android.framework.protobuf.@1 diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt new file mode 100644 index 000000000000..4b2331db7c34 --- /dev/null +++ b/jarjar_rules_hidl.txt @@ -0,0 +1 @@ +rule android.hidl.** android.internal.hidl.@1 diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3c35d9b33fc8..20303eba6667 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -562,7 +562,7 @@ std::string AssetManager2::GetLastResourceResolution() const { if (package != nullptr) { ToResourceName(last_resolution.type_string_ref, last_resolution.entry_string_ref, - package, + package->GetPackageName(), &resource_name); resource_name_string = ToFormattedResourceString(&resource_name); } @@ -607,15 +607,25 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons return false; } - const LoadedPackage* package = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); - if (package == nullptr) { + const uint8_t package_idx = package_ids_[get_package_id(resid)]; + if (package_idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + get_package_id(resid), resid); + return false; + } + + const PackageGroup& package_group = package_groups_[package_idx]; + auto cookie_iter = std::find(package_group.cookies_.begin(), + package_group.cookies_.end(), cookie); + if (cookie_iter == package_group.cookies_.end()) { return false; } + long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter); + const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_; return ToResourceName(entry.type_string_ref, entry.entry_string_ref, - package, + package->GetPackageName(), out_name); } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 645984d85c34..c63dff8f9104 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -48,12 +48,12 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin !(has_type_separator && out_type->empty()); } -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name) { - out_name->package = package->GetPackageName().data(); - out_name->package_len = package->GetPackageName().size(); + out_name->package = package_name.data(); + out_name->package_len = package_name.size(); out_name->type = type_string_ref.string8(&out_name->type_len); out_name->type16 = nullptr; diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index eb6eb8e66175..e649940cdde1 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,12 +28,11 @@ namespace android { bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); -// Convert a type_string_ref, entry_string_ref, and package -// to AssetManager2::ResourceName. Useful for getting -// resource name without re-running AssetManager2::FindEntry searches. -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. +// Useful for getting resource name without re-running AssetManager2::FindEntry searches. +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name); // Formats a ResourceName to "package:type/entry_name". diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 105dcd209bf7..447fdf5d306a 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -210,6 +210,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({lib_one_assets_.get()}); + + AssetManager2::ResourceName name; + ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name)); + std::string formatted_name = ToFormattedResourceString(&name); + ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo"); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 56b1885de820..4c675133a6c1 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -62,10 +62,8 @@ DisplayInfo QueryDisplayInfo() { return displayInfo; } -static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, - sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { +static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { if (Properties::isolatedProcess) { - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); *colorType = SkColorType::kN32_SkColorType; return; @@ -78,16 +76,13 @@ static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status); switch (wcgDataspace) { case ui::Dataspace::DISPLAY_P3: - *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut; *colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); break; case ui::Dataspace::V0_SCRGB: - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); break; case ui::Dataspace::V0_SRGB: // when sRGB is returned, it means wide color gamut is not supported. - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); break; default: @@ -112,7 +107,7 @@ DeviceInfo::DeviceInfo() { mMaxTextureSize = -1; #endif mDisplayInfo = QueryDisplayInfo(); - queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType); + queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType); } int DeviceInfo::maxTextureSize() const { diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 9bcc8e8a3dbe..2bab5d3596cf 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -38,7 +38,6 @@ public: // context or if you are using the HWUI_NULL_GPU int maxTextureSize() const; const DisplayInfo& displayInfo() const { return mDisplayInfo; } - SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; } sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } SkColorType getWideColorType() const { return mWideColorType; } @@ -50,7 +49,6 @@ private: int mMaxTextureSize; DisplayInfo mDisplayInfo; - SkColorSpace::Gamut mWideColorGamut; sk_sp<SkColorSpace> mWideColorSpace; SkColorType mWideColorType; }; diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 635d0ec66673..39bfcdd944a4 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -164,15 +164,11 @@ static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& sourc const SkImageInfo& info = source.info(); bitmap.allocPixels( SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr)); - bitmap.eraseColor(0); - if (info.colorType() == kRGBA_F16_SkColorType) { - // Drawing RGBA_F16 onto ARGB_8888 is not supported - source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), - bitmap.getPixels(), bitmap.rowBytes(), 0, 0); - } else { - SkCanvas canvas(bitmap); - canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); - } + + SkCanvas canvas(bitmap); + canvas.drawColor(0); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + return bitmap; } } @@ -253,8 +249,8 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou eglDestroySyncKHR(display, fence); } - return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(), - Bitmap::computePalette(bitmap)); + return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(), + bitmap.alphaType(), Bitmap::computePalette(bitmap)); } void HardwareBitmapUploader::terminate() { diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index cc62fdc76ef8..54a91f4ec06f 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -682,12 +682,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { if (count <= 0 || paint.nothingToDraw()) return; - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and // older. if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 && @@ -710,12 +709,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) { - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); const int N = end - start; SkTextBlobBuilder builder; diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index dd62bbbdc84f..72656922b03e 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -551,6 +551,19 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) SkPaint paint = inPaint; paint.setAlpha(mProperties.getRootAlpha() * 255); + if (canvas->getGrContext() == nullptr) { + // Recording to picture, don't use the SkSurface which won't work off of renderthread. + Bitmap& bitmap = getBitmapUpdateIfDirty(); + SkBitmap skiaBitmap; + bitmap.getSkBitmap(&skiaBitmap); + + int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); + int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); + canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds, + &paint, SkCanvas::kFast_SrcRectConstraint); + return; + } + SkRect src; sk_sp<SkSurface> vdSurface = mCache.getSurface(&src); if (vdSurface) { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 6e0258c9ecb2..3bbee18c6dd1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -133,12 +133,11 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) } -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace, - SkAlphaType alphaType, BitmapPalette palette) { - // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can - // view the format as RGBA8888. +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType, + sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType, + BitmapPalette palette) { SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, alphaType, colorSpace); + colorType, alphaType, colorSpace); return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette)); } diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 2138040d9690..01e45166e0a3 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -72,6 +72,7 @@ public: * memory that is provided as an input param. */ static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, + SkColorType colorType, sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType = kPremul_SkAlphaType, BitmapPalette palette = BitmapPalette::Unknown); diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 277148e3d556..523148672033 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -39,34 +39,28 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca } void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) { - uint32_t flags; - PaintFilter* paintFilter = getPaintFilter(); - if (paintFilter) { - SkPaint paintCopy(paint); - paintFilter->filter(&paintCopy); - flags = paintCopy.getFlags(); - } else { - flags = paint.getFlags(); - } - if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { + // paint has already been filtered by our caller, so we can ignore any filter + const bool strikeThru = paint.isStrikeThru(); + const bool underline = paint.isUnderline(); + if (strikeThru || underline) { const SkScalar left = x; const SkScalar right = x + length; - if (flags & SkPaint::kUnderlineText_ReserveFlag) { + const float textSize = paint.getSkFont().getSize(); + if (underline) { SkFontMetrics metrics; - paint.getFontMetrics(&metrics); + paint.getSkFont().getMetrics(&metrics); SkScalar position; if (!metrics.hasUnderlinePosition(&position)) { - position = paint.getTextSize() * Paint::kStdUnderline_Top; + position = textSize * Paint::kStdUnderline_Top; } SkScalar thickness; if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + thickness = textSize * Paint::kStdUnderline_Thickness; } const SkScalar top = y + position; drawStroke(left, right, top, thickness, paint, this); } - if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - const float textSize = paint.getTextSize(); + if (strikeThru) { const float position = textSize * Paint::kStdStrikeThru_Top; const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position; @@ -75,19 +69,19 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa } } -static void simplifyPaint(int color, SkPaint* paint) { +static void simplifyPaint(int color, Paint* paint) { paint->setColor(color); paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); + paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); } class DrawTextFunctor { public: - DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x, + DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, float y, minikin::MinikinRect& bounds, float totalAdvance) : layout(layout) , canvas(canvas) @@ -123,14 +117,14 @@ public: bool darken = channelSum < (128 * 3); // outline - SkPaint outlinePaint(paint); + Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); // inner - SkPaint innerPaint(paint); + Paint innerPaint(paint); simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); innerPaint.setStyle(SkPaint::kFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop, @@ -145,7 +139,7 @@ public: private: const minikin::Layout& layout; Canvas* canvas; - const SkPaint& paint; + const Paint& paint; float x; float y; minikin::MinikinRect& bounds; diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 84292c8768c1..375f5bc9df37 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -17,8 +17,9 @@ #include "MinikinSkia.h" #include <SkFontDescriptor.h> +#include <SkFont.h> +#include <SkFontMetrics.h> #include <SkFontMgr.h> -#include <SkPaint.h> #include <SkTypeface.h> #include <log/log.h> @@ -40,25 +41,24 @@ MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontDat , mAxes(axes) , mFilePath(filePath) {} -static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint, - const minikin::MinikinPaint& paint, - const minikin::FontFakery& fakery) { - skPaint->setTextEncoding(kGlyphID_SkTextEncoding); - skPaint->setTextSize(paint.size); - skPaint->setTextScaleX(paint.scaleX); - skPaint->setTextSkewX(paint.skewX); - MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); +static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery) { + skFont->setSize(paint.size); + skFont->setScaleX(paint.scaleX); + skFont->setSkewX(paint.skewX); + MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags); // Apply font fakery on top of user-supplied flags. - MinikinFontSkia::populateSkPaint(skPaint, font, fakery); + MinikinFontSkia::populateSkFont(skFont, font, fakery); } float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkScalar skWidth; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, &skWidth); #ifdef VERBOSE ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth); #endif @@ -68,11 +68,11 @@ float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::Mi void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkRect skBounds; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, nullptr, &skBounds); bounds->mLeft = skBounds.fLeft; bounds->mTop = skBounds.fTop; bounds->mRight = skBounds.fRight; @@ -82,10 +82,10 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); + SkFont skFont; + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); SkFontMetrics metrics; - skPaint.getFontMetrics(&metrics); + skFont.getMetrics(&metrics); extent->ascent = metrics.fAscent; extent->descent = metrics.fDescent; } @@ -137,28 +137,36 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( ttcIndex, variations); } -uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { - uint32_t flags = paint->getFlags(); - unsigned hinting = static_cast<unsigned>(paint->getHinting()); - // select only flags that might affect text layout - flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | - SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag | - SkPaint::kAutoHinting_Flag); - flags |= (hinting << 16); +// hinting<<16 | edging<<8 | bools:5bits +uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) { + uint32_t flags = (unsigned)font.getHinting() << 16; + flags |= (unsigned)font.getEdging() << 8; + flags |= font.isEmbolden() << minikin::Embolden_Shift; + flags |= font.isLinearMetrics() << minikin::LinearMetrics_Shift; + flags |= font.isSubpixel() << minikin::Subpixel_Shift; + flags |= font.isEmbeddedBitmaps() << minikin::EmbeddedBitmaps_Shift; + flags |= font.isForceAutoHinting() << minikin::ForceAutoHinting_Shift; return flags; } -void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { - paint->setFlags(paintFlags & SkPaint::kAllFlags); - paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16)); +void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) { + // We store hinting in the top 16 bits (only need 2 of them) + font->setHinting((SkFontHinting)(flags >> 16)); + // We store edging in bits 8:15 (only need 2 of them) + font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF)); + font->setEmbolden( (flags & minikin::Embolden_Flag) != 0); + font->setLinearMetrics( (flags & minikin::LinearMetrics_Flag) != 0); + font->setSubpixel( (flags & minikin::Subpixel_Flag) != 0); + font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0); + font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0); } -void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, - minikin::FontFakery fakery) { - paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); - paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold()); +void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font, + minikin::FontFakery fakery) { + skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); + skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold()); if (fakery.isFakeItalic()) { - paint->setTextSkewX(paint->getTextSkewX() - 0.25f); + skFont->setSkewX(skFont->getSkewX() - 0.25f); } } } diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index 55576b7bfa4e..ad46b2391cac 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -21,7 +21,7 @@ #include <cutils/compiler.h> #include <minikin/MinikinFont.h> -class SkPaint; +class SkFont; class SkTypeface; namespace android { @@ -54,12 +54,12 @@ public: std::shared_ptr<minikin::MinikinFont> createFontWithVariation( const std::vector<minikin::FontVariation>&) const; - static uint32_t packPaintFlags(const SkPaint* paint); - static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); + static uint32_t packFontFlags(const SkFont&); + static void unpackFontFlags(SkFont*, uint32_t fontFlags); // set typeface and fake bold/italic parameters - static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font, - minikin::FontFakery fakery); + static void populateSkFont(SkFont*, const minikin::MinikinFont* font, + minikin::FontFakery fakery); private: sk_sp<SkTypeface> mTypeface; diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index ba240feb4f41..733f8e415270 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -30,16 +30,17 @@ namespace android { minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, const Typeface* typeface) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + const SkFont& font = paint->getSkFont(); minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection); /* Prepare minikin Paint */ minikinPaint.size = - paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize()); - minikinPaint.scaleX = paint->getTextScaleX(); - minikinPaint.skewX = paint->getTextSkewX(); + font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize()); + minikinPaint.scaleX = font.getScaleX(); + minikinPaint.skewX = font.getSkewX(); minikinPaint.letterSpacing = paint->getLetterSpacing(); minikinPaint.wordSpacing = paint->getWordSpacing(); - minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint); + minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font); minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index d27d54454ea0..cbf409504675 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -63,27 +63,29 @@ public: // f is a functor of type void f(size_t start, size_t end); template <typename F> ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) { - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); + float saveSkewX = paint->getSkFont().getSkewX(); + bool savefakeBold = paint->getSkFont().isEmbolden(); const minikin::MinikinFont* curFont = nullptr; size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { const minikin::MinikinFont* nextFont = layout.getFont(i); if (i > 0 && nextFont != curFont) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, i); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); start = i; } curFont = nextFont; } if (nGlyphs > start) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, nGlyphs); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); } } }; diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 92ffda9486bb..601b3c23cc1f 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -21,6 +21,7 @@ #include <cutils/compiler.h> +#include <SkFont.h> #include <SkPaint.h> #include <string> @@ -46,7 +47,6 @@ public: Paint(); Paint(const Paint& paint); - Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor) ~Paint(); Paint& operator=(const Paint& other); @@ -54,6 +54,17 @@ public: friend bool operator==(const Paint& a, const Paint& b); friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); } + SkFont& getSkFont() { return mFont; } + const SkFont& getSkFont() const { return mFont; } + + // These shadow the methods on SkPaint, but we need to so we can keep related + // attributes in-sync. + + void reset(); + void setAntiAlias(bool); + + // End method shadowing + void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; } float getLetterSpacing() const { return mLetterSpacing; } @@ -94,7 +105,31 @@ public: Align getTextAlign() const { return mAlign; } void setTextAlign(Align align) { mAlign = align; } + bool isStrikeThru() const { return mStrikeThru; } + void setStrikeThru(bool st) { mStrikeThru = st; } + + bool isUnderline() const { return mUnderline; } + void setUnderline(bool u) { mUnderline = u; } + + bool isDevKern() const { return mDevKern; } + void setDevKern(bool d) { mDevKern = d; } + + // The Java flags (Paint.java) no longer fit into the native apis directly. + // These methods handle converting to and from them and the native representations + // in android::Paint. + + uint32_t getJavaFlags() const; + void setJavaFlags(uint32_t); + + // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags + // that are meant for SkFont or Paint (e.g. underline, strikethru) + // The only respected flags are : [ antialias, dither, filterBitmap ] + static uint32_t GetSkPaintJavaFlags(const SkPaint&); + static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags); + private: + SkFont mFont; + float mLetterSpacing = 0; float mWordSpacing = 0; std::string mFontFeatureSettings; @@ -107,6 +142,9 @@ private: // nullptr is valid: it means the default typeface. const Typeface* mTypeface = nullptr; Align mAlign = kLeft_Align; + bool mStrikeThru = false; + bool mUnderline = false; + bool mDevKern = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h index bf5627eac229..0e7b61977000 100644 --- a/libs/hwui/hwui/PaintFilter.h +++ b/libs/hwui/hwui/PaintFilter.h @@ -12,6 +12,7 @@ public: * The implementation may modify the paint as they wish. */ virtual void filter(SkPaint*) = 0; + virtual void filterFullPaint(Paint*) = 0; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index bdbf5cacaaf0..2f2d575bca29 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,10 +24,16 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) { + // SkPaint::antialiasing defaults to false, but + // SkFont::edging defaults to kAntiAlias. To keep them + // insync, we manually set the font to kAilas. + mFont.setEdging(SkFont::Edging::kAlias); +} Paint::Paint(const Paint& paint) : SkPaint(paint) + , mFont(paint.mFont) , mLetterSpacing(paint.mLetterSpacing) , mWordSpacing(paint.mWordSpacing) , mFontFeatureSettings(paint.mFontFeatureSettings) @@ -35,20 +41,17 @@ Paint::Paint(const Paint& paint) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) , mTypeface(paint.mTypeface) - , mAlign(paint.mAlign) {} + , mAlign(paint.mAlign) + , mStrikeThru(paint.mStrikeThru) + , mUnderline(paint.mUnderline) + , mDevKern(paint.mDevKern) {} -Paint::Paint(const SkPaint& paint) - : SkPaint(paint) - , mLetterSpacing(0) - , mWordSpacing(0) - , mFontFeatureSettings() - , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} Paint::~Paint() {} Paint& Paint::operator=(const Paint& other) { SkPaint::operator=(other); + mFont = other.mFont; mLetterSpacing = other.mLetterSpacing; mWordSpacing = other.mWordSpacing; mFontFeatureSettings = other.mFontFeatureSettings; @@ -57,15 +60,136 @@ Paint& Paint::operator=(const Paint& other) { mHyphenEdit = other.mHyphenEdit; mTypeface = other.mTypeface; mAlign = other.mAlign; + mStrikeThru = other.mStrikeThru; + mUnderline = other.mUnderline; + mDevKern = other.mDevKern; return *this; } bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && + a.mFont == b.mFont && a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && - a.mTypeface == b.mTypeface && a.mAlign == b.mAlign; + a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && + a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && + a.mDevKern == b.mDevKern; +} + +void Paint::reset() { + SkPaint::reset(); + + mFont = SkFont(); + mFont.setEdging(SkFont::Edging::kAlias); + + mStrikeThru = false; + mUnderline = false; + mDevKern = false; +} + +void Paint::setAntiAlias(bool aa) { + // Java does not support/understand subpixel(lcd) antialiasing + SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias); + // JavaPaint antialiasing affects both the SkPaint and SkFont settings. + SkPaint::setAntiAlias(aa); + mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); +} + +////////////////// Java flags compatibility ////////////////// + +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really + match up with skia anymore, so we have to do some shuffling in get/set flags() + + 3 flags apply to SkPaint (antialias, dither, filter -> enum) + 5 flags (merged with antialias) are for SkFont + 2 flags are for minikin::Paint (underline and strikethru) +*/ + +// flags relating to SkPaint +static const uint32_t sAntiAliasFlag = 0x01; // affects paint and font-edging +static const uint32_t sFilterBitmapFlag = 0x02; // maps to enum +static const uint32_t sDitherFlag = 0x04; +// flags relating to SkFont +static const uint32_t sFakeBoldFlag = 0x020; +static const uint32_t sLinearMetrics = 0x040; +static const uint32_t sSubpixelMetrics = 0x080; +static const uint32_t sEmbeddedBitmaps = 0x400; +static const uint32_t sForceAutoHinting = 0x800; +// flags related to minikin::Paint +static const uint32_t sUnderlineFlag = 0x08; +static const uint32_t sStrikeThruFlag = 0x10; +// flags no longer supported on native side (but mirrored for compatibility) +static const uint32_t sDevKernFlag = 0x100; + +static uint32_t paintToLegacyFlags(const SkPaint& paint) { + uint32_t flags = 0; + flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag; + flags |= -(int)paint.isDither() & sDitherFlag; + if (paint.getFilterQuality() != kNone_SkFilterQuality) { + flags |= sFilterBitmapFlag; + } + return flags; } + +static uint32_t fontToLegacyFlags(const SkFont& font) { + uint32_t flags = 0; + flags |= -(int)font.isEmbolden() & sFakeBoldFlag; + flags |= -(int)font.isLinearMetrics() & sLinearMetrics; + flags |= -(int)font.isSubpixel() & sSubpixelMetrics; + flags |= -(int)font.isEmbeddedBitmaps() & sEmbeddedBitmaps; + flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting; + return flags; +} + +static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) { + paint->setAntiAlias((flags & sAntiAliasFlag) != 0); + paint->setDither ((flags & sDitherFlag) != 0); + + if (flags & sFilterBitmapFlag) { + paint->setFilterQuality(kLow_SkFilterQuality); + } else { + paint->setFilterQuality(kNone_SkFilterQuality); + } +} + +static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) { + font->setEmbolden ((flags & sFakeBoldFlag) != 0); + font->setLinearMetrics ((flags & sLinearMetrics) != 0); + font->setSubpixel ((flags & sSubpixelMetrics) != 0); + font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0); + font->setForceAutoHinting((flags & sForceAutoHinting) != 0); + + if (flags & sAntiAliasFlag) { + font->setEdging(SkFont::Edging::kAntiAlias); + } else { + font->setEdging(SkFont::Edging::kAlias); + } +} + +uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) { + return paintToLegacyFlags(paint); +} + +void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) { + applyLegacyFlagsToPaint(flags, paint); +} + +uint32_t Paint::getJavaFlags() const { + uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont); + flags |= -(int)mStrikeThru & sStrikeThruFlag; + flags |= -(int)mUnderline & sUnderlineFlag; + flags |= -(int)mDevKern & sDevKernFlag; + return flags; +} + +void Paint::setJavaFlags(uint32_t flags) { + applyLegacyFlagsToPaint(flags, this); + applyLegacyFlagsToFont(flags, &mFont); + mStrikeThru = (flags & sStrikeThruFlag) != 0; + mUnderline = (flags & sUnderlineFlag) != 0; + mDevKern = (flags & sDevKernFlag) != 0; +} + } // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 240efb41285c..a1b2b18195bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -74,7 +74,13 @@ static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSiz void GLFunctorDrawable::onDraw(SkCanvas* canvas) { if (canvas->getGrContext() == nullptr) { - SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface")); + // We're dumping a picture, render a light-blue rectangle instead + // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't + // seem to have a default typeface that works. We only ever use drawGlyphs, which + // requires going through minikin & hwui's canvas which we don't have here. + SkPaint paint; + paint.setColor(0xFF81D4FA); + canvas->drawRect(mBounds, paint); return; } @@ -132,6 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = fboSize.width(); info.height = fboSize.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear // the stencil and/or draw the functor. diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 1661905eff57..8508274676fd 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -31,8 +31,8 @@ namespace skiapipeline { // Cache size limits. static const size_t maxKeySize = 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 512 * 1024; +static const size_t maxValueSize = 512 * 1024; +static const size_t maxTotalSize = 1024 * 1024; ShaderCache::ShaderCache() { // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header. diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index cfbb9956d3dc..570e895a012d 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -167,7 +167,7 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut); + auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); if (!newSurface) { return false; } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index df8224372ea7..a00a36f93501 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -111,7 +111,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) const Rect& layerDamage = layers.entries()[i].damage; - SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface()); + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); int saveCount = layerCanvas->save(); SkASSERT(saveCount == 1); @@ -139,8 +139,6 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) layerCanvas->restoreToCount(saveCount); mLightCenter = savedLightCenter; - endCapture(layerNode->getLayerSurface()); - // cache the current context so that we can defer flushing it until // either all the layers have been rendered or the context changes GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext(); @@ -244,6 +242,7 @@ public: } virtual void onProcess(const sp<Task<bool>>& task) override { + ATRACE_NAME("SavePictureTask"); SavePictureTask* t = static_cast<SavePictureTask*>(task.get()); if (0 == access(t->filename.c_str(), F_OK)) { @@ -265,46 +264,56 @@ public: SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { - bool recordingPicture = mCaptureSequence > 0; char prop[PROPERTY_VALUE_MAX] = {'\0'}; - if (!recordingPicture) { + if (mCaptureSequence <= 0) { property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0"); - recordingPicture = prop[0] != '0' && - mCapturedFile != prop; // ensure we capture only once per filename - if (recordingPicture) { + if (prop[0] != '0' && mCapturedFile != prop) { mCapturedFile = prop; mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1); } } - if (recordingPicture) { + if (mCaptureSequence > 0 || mPictureCapturedCallback) { mRecorder.reset(new SkPictureRecorder()); - return mRecorder->beginRecording(surface->width(), surface->height(), nullptr, - SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr, + SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height()); + mNwayCanvas->addCanvas(surface->getCanvas()); + mNwayCanvas->addCanvas(pictureCanvas); + return mNwayCanvas.get(); } } return surface->getCanvas(); } void SkiaPipeline::endCapture(SkSurface* surface) { + mNwayCanvas.reset(); if (CC_UNLIKELY(mRecorder.get())) { + ATRACE_CALL(); sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); - surface->getCanvas()->drawPicture(picture); if (picture->approximateOpCount() > 0) { - auto data = picture->serialize(); - - // offload saving to file in a different thread - if (!mSavePictureProcessor.get()) { - TaskManager* taskManager = getTaskManager(); - mSavePictureProcessor = new SavePictureProcessor( - taskManager->canRunTasks() ? taskManager : nullptr); + if (mCaptureSequence > 0) { + ATRACE_BEGIN("picture->serialize"); + auto data = picture->serialize(); + ATRACE_END(); + + // offload saving to file in a different thread + if (!mSavePictureProcessor.get()) { + TaskManager* taskManager = getTaskManager(); + mSavePictureProcessor = new SavePictureProcessor( + taskManager->canRunTasks() ? taskManager : nullptr); + } + if (1 == mCaptureSequence) { + mSavePictureProcessor->savePicture(data, mCapturedFile); + } else { + mSavePictureProcessor->savePicture( + data, + mCapturedFile + "_" + std::to_string(mCaptureSequence)); + } + mCaptureSequence--; } - if (1 == mCaptureSequence) { - mSavePictureProcessor->savePicture(data, mCapturedFile); - } else { - mSavePictureProcessor->savePicture( - data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); + if (mPictureCapturedCallback) { + std::invoke(mPictureCapturedCallback, std::move(picture)); } - mCaptureSequence--; } mRecorder.reset(); } @@ -314,6 +323,11 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform) { + bool previousSkpEnabled = Properties::skpCaptureEnabled; + if (mPictureCapturedCallback) { + Properties::skpCaptureEnabled = true; + } + renderVectorDrawableCache(); // draw all layers up front @@ -334,6 +348,8 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli ATRACE_NAME("flush commands"); surface->getCanvas()->flush(); + + Properties::skpCaptureEnabled = previousSkpEnabled; } namespace { @@ -460,11 +476,9 @@ void SkiaPipeline::dumpResourceCacheUsage() const { void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { if (colorMode == ColorMode::SRGB) { mSurfaceColorType = SkColorType::kN32_SkColorType; - mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); } else if (colorMode == ColorMode::WideColorGamut) { mSurfaceColorType = DeviceInfo::get()->getWideColorType(); - mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut(); mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); } else { LOG_ALWAYS_FATAL("Unreachable: unsupported color mode."); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index cf6f5b284d8c..7381e0417a2d 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -105,13 +105,17 @@ public: mLightCenter = lightGeometry.center; } + void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) override { + mPictureCapturedCallback = callback; + } + protected: void dumpResourceCacheUsage() const; void setSurfaceColorProperties(renderthread::ColorMode colorMode); renderthread::RenderThread& mRenderThread; SkColorType mSurfaceColorType; - SkColorSpace::Gamut mSurfaceColorGamut; sk_sp<SkColorSpace> mSurfaceColorSpace; private: @@ -163,6 +167,8 @@ private: * parallel tryCapture calls (not really needed). */ std::unique_ptr<SkPictureRecorder> mRecorder; + std::unique_ptr<SkNWayCanvas> mNwayCanvas; + std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; static float mLightRadius; static uint8_t mAmbientShadowAlpha; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 53495a7d62c0..d0fe022616c5 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -129,7 +129,7 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh setSurfaceColorProperties(colorMode); if (surface) { mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, - mSurfaceColorGamut, mSurfaceColorType); + mSurfaceColorType); } return mVkSurface != nullptr; @@ -149,20 +149,8 @@ void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* func sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread, SkBitmap& skBitmap) { - // TODO: implement this function for Vulkan pipeline - // code below is a hack to avoid crashing because of missing HW Bitmap support - sp<GraphicBuffer> buffer = new GraphicBuffer( - skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, - std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") + - std::to_string(getpid()) + "]"); - status_t error = buffer->initCheck(); - if (error < 0) { - ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()"); - return nullptr; - } - return Bitmap::createFrom(buffer, skBitmap.refColorSpace()); + LOG_ALWAYS_FATAL("Unimplemented"); + return nullptr; } } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index c3563dbfd3cd..706325f00bd2 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -132,6 +132,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = mFBInfo.width(); info.height = mFBInfo.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); @@ -179,8 +180,8 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { canvas->resetMatrix(); auto functorImage = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr, - kBottomLeft_GrSurfaceOrigin); + reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, + canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin); canvas->drawImage(functorImage, 0, 0, &paint); canvas->restore(); } diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index 9e1bb8e8e548..501b8df9bc36 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_DRAW_GL_INFO_H #define ANDROID_HWUI_DRAW_GL_INFO_H +#include <SkColorSpace.h> + namespace android { namespace uirenderer { @@ -41,6 +43,9 @@ struct DrawGlInfo { // Input: current transform matrix, in OpenGL format float transform[16]; + // Input: Color space. + const SkColorSpace* color_space_ptr; + // Output: dirty region to redraw float dirtyLeft; float dirtyTop; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 9e7abf447cd6..db97763acd8b 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -184,6 +184,10 @@ public: mFrameCompleteCallbacks.push_back(std::move(func)); } + void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) { + mRenderPipeline->setPictureCapturedCallback(callback); + } + void setForceDark(bool enable) { mUseForceDark = enable; } diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 8cd97ed20215..2cc3f362e172 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -132,11 +132,13 @@ void EglManager::initialize() { createPBufferSurface(); makeCurrent(mPBufferSurface, nullptr, /* force */ true); - SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut(); + skcms_Matrix3x3 wideColorGamut; + LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut), + "Could not get gamut matrix from wideColorSpace"); bool hasWideColorSpaceExtension = false; - if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) { hasWideColorSpaceExtension = EglExtensions.displayP3; - } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) { hasWideColorSpaceExtension = EglExtensions.scRGB; } else { LOG_ALWAYS_FATAL("Unsupported wide color space."); @@ -297,7 +299,7 @@ void EglManager::createPBufferSurface() { Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode, - SkColorSpace::Gamut colorGamut) { + sk_sp<SkColorSpace> colorSpace) { LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized"); bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport && @@ -330,15 +332,15 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, if (EglExtensions.glColorSpace) { attribs[0] = EGL_GL_COLORSPACE_KHR; if (wideColorGamut) { - switch (colorGamut) { - case SkColorSpace::Gamut::kDCIP3_D65_Gamut: - attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; - break; - case SkColorSpace::Gamut::kSRGB_Gamut: - attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; - break; - default: - LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + skcms_Matrix3x3 colorGamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); } } else { attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 4dd90961b4f7..27d41d26a73a 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -49,7 +49,7 @@ public: bool hasEglContext(); Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode, - SkColorSpace::Gamut colorGamut); + sk_sp<SkColorSpace> colorSpace); void destroySurface(EGLSurface surface); void destroy(); diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index d4dd62941440..2cfc8df38297 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -59,15 +59,15 @@ public: virtual MakeCurrentResult makeCurrent() = 0; virtual Frame getFrame() = 0; virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, - bool opaque, const LightInfo& lightInfo, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; + virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, + ColorMode colorMode) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; virtual bool isContextReady() = 0; @@ -85,6 +85,8 @@ public: virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; virtual GrSurfaceOrigin getSurfaceOrigin() = 0; + virtual void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0; virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index aa6af23d8ed3..720c60362a55 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -21,6 +21,7 @@ #include "Properties.h" #include "Readback.h" #include "Rect.h" +#include "WebViewFunctorManager.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/VectorDrawableAtlas.h" #include "renderstate/RenderState.h" @@ -30,7 +31,6 @@ #include "renderthread/RenderThread.h" #include "utils/Macros.h" #include "utils/TimeUtils.h" -#include "WebViewFunctorManager.h" #include <ui/GraphicBuffer.h> @@ -147,9 +147,7 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) { void RenderProxy::destroyFunctor(int functor) { ATRACE_CALL(); RenderThread& thread = RenderThread::getInstance(); - thread.queue().post([=]() { - WebViewFunctorManager::instance().destroyFunctor(functor); - }); + thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); }); } DeferredLayerUpdater* RenderProxy::createTextureLayer() { @@ -164,9 +162,9 @@ void RenderProxy::buildLayer(RenderNode* node) { bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) { auto& thread = RenderThread::getInstance(); - return thread.queue().runSync( - [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap) - == CopyResult::Success; }); + return thread.queue().runSync([&]() -> bool { + return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success; + }); } void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -204,9 +202,8 @@ void RenderProxy::fence() { } int RenderProxy::maxTextureSize() { - static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() { - return DeviceInfo::get()->maxTextureSize(); - }); + static int maxTextureSize = RenderThread::getInstance().queue().runSync( + []() { return DeviceInfo::get()->maxTextureSize(); }); return maxTextureSize; } @@ -244,8 +241,10 @@ uint32_t RenderProxy::frameTimePercentile(int percentile) { } void RenderProxy::dumpGraphicsMemory(int fd) { - auto& thread = RenderThread::getInstance(); - thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); }); + if (RenderThread::hasInstance()) { + auto& thread = RenderThread::getInstance(); + thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); }); + } } void RenderProxy::setProcessStatsBuffer(int fd) { @@ -281,6 +280,12 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } +void RenderProxy::setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) { + mRenderThread.queue().post( + [ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); }); +} + void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) { mDrawFrameTask.setFrameCallback(std::move(callback)); } @@ -302,9 +307,7 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) } void RenderProxy::setForceDark(bool enable) { - mRenderThread.queue().post([this, enable]() { - mContext->setForceDark(enable); - }); + mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); }); } int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, @@ -348,9 +351,8 @@ int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { // TODO: fix everything that hits this. We should never be triggering a readback ourselves. return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); } else { - return thread.queue().runSync([&]() -> int { - return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); - }); + return thread.queue().runSync( + [&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); }); } } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 9dc918121be6..6e1bfd74528a 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -114,6 +114,8 @@ public: ANDROID_API void removeRenderNode(RenderNode* node); ANDROID_API void drawRenderNode(RenderNode* node); ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom); + ANDROID_API void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback); ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback); ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 5c6cb9ad43db..1e7520216d66 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -516,10 +516,9 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) { if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) { ColorMode colorMode = surface->mColorMode; sk_sp<SkColorSpace> colorSpace = surface->mColorSpace; - SkColorSpace::Gamut colorGamut = surface->mColorGamut; SkColorType colorType = surface->mColorType; destroySurface(surface); - *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType); + *surfaceOut = createSurface(window, colorMode, colorSpace, colorType); surface = *surfaceOut; } @@ -841,9 +840,12 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { } if (surface->mColorMode == ColorMode::WideColorGamut) { - if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + skcms_Matrix3x3 surfaceGamut; + LOG_ALWAYS_FATAL_IF(!surface->mColorSpace->toXYZD50(&surfaceGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) { colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; - } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) { colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT; } else { LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); @@ -922,7 +924,6 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorSpace::Gamut surfaceColorGamut, SkColorType surfaceColorType) { initialize(); @@ -931,7 +932,7 @@ VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode col } VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace, - surfaceColorGamut, surfaceColorType); + surfaceColorType); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index b06eb82dac79..abe78efc9174 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -39,9 +39,9 @@ class RenderThread; class VulkanSurface { public: VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace, - SkColorSpace::Gamut colorGamut, SkColorType colorType) + SkColorType colorType) : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace), - mColorGamut(colorGamut), mColorType(colorType) {} + mColorType(colorType) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -90,7 +90,6 @@ private: int mWindowWidth = 0; int mWindowHeight = 0; sk_sp<SkColorSpace> mColorSpace; - SkColorSpace::Gamut mColorGamut; SkColorType mColorType; VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; SkMatrix mPreTransform; @@ -113,7 +112,6 @@ public: // VulkanSurface object which is returned. VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorSpace::Gamut surfaceColorGamut, SkColorType surfaceColorType); // Destroy the VulkanSurface and all associated vulkan objects. diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 16a27598c56a..a9f651d38a06 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -78,24 +78,21 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( return layerUpdater; } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); uint32_t length = strlen(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); + canvas->drawText(utf16.get(), length, // text buffer 0, length, // draw range 0, length, // context range - x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */); + x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */); } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, nullptr); } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 6a1ca5a25361..e7124df72beb 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -278,10 +278,10 @@ public: static SkColor interpolateColor(float fraction, SkColor start, SkColor end); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path); static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str); diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index f0a5e9dff1b9..0795d13f441b 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -51,7 +51,7 @@ public: paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { - paint.setTextSize(10 + (frameNr % 20) + i * 20); + paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20); TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index ec81f629ee45..2af955fbb711 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -50,7 +50,8 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB())); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType, + SkColorSpace::MakeSRGB())); sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 58c99800875b..ecaaf487e4f8 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -16,7 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" - +#include "hwui/Paint.h" #include <SkGradientShader.h> class ListOfFadedTextAnimation; @@ -33,8 +33,8 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { canvas.drawColor(Color::White, SkBlendMode::kSrcOver); int length = dp(100); canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer); - SkPaint textPaint; - textPaint.setTextSize(dp(20)); + Paint textPaint; + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index 4111bd24847e..feb881f654f8 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" +#include "hwui/Paint.h" #include <SkFont.h> #include <cstdio> @@ -83,14 +84,14 @@ class ListViewAnimation : public TestListViewSceneBase { roundRectPaint.setColor(Color::White); canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); - SkPaint textPaint; + Paint textPaint; textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); - textPaint.setTextSize(dp(20)); + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); char buf[256]; snprintf(buf, sizeof(buf), "This card is #%d", cardId); TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25)); - textPaint.setTextSize(dp(15)); + textPaint.getSkFont().setSize(dp(15)); TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint, itemHeight, dp(45)); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index aa537b4f329c..f6cff1c643a1 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "renderthread/RenderProxy.h" #include "utils/Color.h" +#include "hwui/Paint.h" class MagnifierAnimation; @@ -37,9 +38,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 3befce4a395f..8630be87c09c 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -41,9 +41,9 @@ public: int top = bounds.fTop; mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); - mBluePaint.setTextSize(padding); + mBluePaint.getSkFont().setSize(padding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); - mGreenPaint.setTextSize(padding); + mGreenPaint.getSkFont().setSize(padding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a16b17849fc6..d30903679bce 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -15,6 +15,7 @@ */ #include "TestSceneBase.h" +#include "hwui/Paint.h" class TextAnimation; @@ -28,9 +29,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); for (int i = 0; i < 10; i++) { diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index 286f5f194aed..229c7f392629 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -17,6 +17,7 @@ #include "SkBlendMode.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" +#include "hwui/Paint.h" class TvApp; class TvAppNoRoundedCorner; @@ -116,13 +117,13 @@ private: [text, text2](RenderProperties& props, Canvas& canvas) { canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(24); + paint.getSkFont().setSize(24); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30); - paint.setTextSize(20); + paint.getSkFont().setSize(20); TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54); }); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 4415a593f6ee..d14116f7b555 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -45,6 +45,20 @@ android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) { } } +SkColorType PixelFormatToColorType(android::PixelFormat format) { + switch (format) { + case PIXEL_FORMAT_RGBX_8888: return kRGB_888x_SkColorType; + case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType; + case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; + case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType; + case PIXEL_FORMAT_RGBA_4444: return kARGB_4444_SkColorType; + default: + ALOGW("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format); + return kUnknown_SkColorType; + } +} + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { skcms_Matrix3x3 gamut; diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 388025207ed6..b67d10d4249c 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -112,6 +112,7 @@ static constexpr float EOCF(float srgb) { } android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); +ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format); ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); diff --git a/media/Android.bp b/media/Android.bp index 0eb86acf9ecf..0675a36f3e81 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -1,25 +1,59 @@ java_library { - name: "media1", + name: "updatable-media1", srcs: [ ":media1-srcs", + ":framework-media-annotation-srcs", ], + aidl: { + export_include_dirs: [ + "apex/java", + ], + + // TODO: find out a way to include only the necessary aidl files instead of dirs. + include_dirs: [ + "frameworks/base/core/java", + "frameworks/base/media/java", + ], + }, + + installable: true, + + // Make sure that the implementaion only relies on SDK or system APIs. sdk_version: "system_current", } -filegroup { - name: "media1-srcs", +java_library { + name: "updatable-mediasession2", + srcs: [ - "java/android/media/session/MediaSessionProviderService.java", + ":mediasession2-srcs", + ":framework-media-annotation-srcs", ], + + aidl: { + export_include_dirs: [ + "apex/java", + ], + + // TODO: find out a way to include only the necessary aidl files instead of dirs. + include_dirs: [ + "frameworks/base/core/java", + ], + }, + + installable: true, + + // Make sure that the implementaion only relies on SDK or system APIs. + sdk_version: "system_current", } java_library { name: "updatable-media", srcs: [ - ":media2-srcs", + ":mediaplayer2-srcs", ":framework-media-annotation-srcs", ], @@ -34,7 +68,87 @@ java_library { } filegroup { - name: "media2-srcs", + name: "media-srcs-without-aidls", + srcs : [ + ":media1-srcs-without-aidls", + ":mediasession2-srcs-without-aidls", + ":mediaplayer2-srcs", + ], +} + +filegroup { + name: "media1-srcs", + srcs: [ + "apex/java/android/media/MediaMetadata.java", + "apex/java/android/media/MediaParceledListSlice.java", + "apex/java/android/media/VolumeProvider.java", + "apex/java/android/media/browse/MediaBrowser.java", + "apex/java/android/media/browse/MediaBrowserUtils.java", + "apex/java/android/media/session/ControllerCallbackLink.java", + "apex/java/android/media/session/ControllerLink.java", + "apex/java/android/media/session/ISession.aidl", + "apex/java/android/media/session/ISessionCallback.aidl", + "apex/java/android/media/session/ISessionController.aidl", + "apex/java/android/media/session/ISessionControllerCallback.aidl", + "apex/java/android/media/session/MediaController.java", + "apex/java/android/media/session/MediaSessionEngine.java", + "apex/java/android/media/session/MediaSessionProviderService.java", + "apex/java/android/media/session/PlaybackState.java", + "apex/java/android/media/session/SessionCallbackLink.java", + "apex/java/android/media/session/SessionLink.java", + "apex/java/android/service/media/IMediaBrowserService.aidl", + "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl", + "apex/java/android/service/media/MediaBrowserService.java", + ], +} + +filegroup { + name: "media1-srcs-without-aidls", + srcs: [ + ":media1-srcs", + ], + exclude_srcs: [ + "apex/java/android/media/session/ISession.aidl", + "apex/java/android/media/session/ISessionCallback.aidl", + "apex/java/android/media/session/ISessionController.aidl", + "apex/java/android/media/session/ISessionControllerCallback.aidl", + "apex/java/android/service/media/IMediaBrowserService.aidl", + "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl", + ], +} + +filegroup { + name: "mediasession2-srcs", + srcs: [ + "apex/java/android/media/Controller2Link.java", + "apex/java/android/media/IMediaController2.aidl", + "apex/java/android/media/IMediaSession2.aidl", + "apex/java/android/media/IMediaSession2Service.aidl", + "apex/java/android/media/MediaConstants.java", + "apex/java/android/media/MediaController2.java", + "apex/java/android/media/MediaItem2.java", + "apex/java/android/media/MediaSession2.java", + "apex/java/android/media/MediaSession2Service.java", + "apex/java/android/media/Session2Command.java", + "apex/java/android/media/Session2CommandGroup.java", + "apex/java/android/media/Session2Link.java", + ], +} + +filegroup { + name: "mediasession2-srcs-without-aidls", + srcs: [ + ":mediasession2-srcs", + ], + exclude_srcs: [ + "apex/java/android/media/IMediaController2.aidl", + "apex/java/android/media/IMediaSession2.aidl", + "apex/java/android/media/IMediaSession2Service.aidl", + ], +} + +filegroup { + name: "mediaplayer2-srcs", srcs: [ "apex/java/android/media/CloseGuard.java", "apex/java/android/media/DataSourceCallback.java", diff --git a/media/java/android/media/Controller2Link.aidl b/media/apex/java/android/media/Controller2Link.aidl index 64edafcb11fc..64edafcb11fc 100644 --- a/media/java/android/media/Controller2Link.aidl +++ b/media/apex/java/android/media/Controller2Link.aidl diff --git a/media/java/android/media/Controller2Link.java b/media/apex/java/android/media/Controller2Link.java index d11f7769ee5e..d11f7769ee5e 100644 --- a/media/java/android/media/Controller2Link.java +++ b/media/apex/java/android/media/Controller2Link.java diff --git a/media/java/android/media/IMediaController2.aidl b/media/apex/java/android/media/IMediaController2.aidl index 42c6e70529ec..42c6e70529ec 100644 --- a/media/java/android/media/IMediaController2.aidl +++ b/media/apex/java/android/media/IMediaController2.aidl diff --git a/media/java/android/media/IMediaSession2.aidl b/media/apex/java/android/media/IMediaSession2.aidl index 26e717b39afc..26e717b39afc 100644 --- a/media/java/android/media/IMediaSession2.aidl +++ b/media/apex/java/android/media/IMediaSession2.aidl diff --git a/media/java/android/media/IMediaSession2Service.aidl b/media/apex/java/android/media/IMediaSession2Service.aidl index 10ac1be0a36e..10ac1be0a36e 100644 --- a/media/java/android/media/IMediaSession2Service.aidl +++ b/media/apex/java/android/media/IMediaSession2Service.aidl diff --git a/media/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java index 65b6f55a068a..45ea8261c6fb 100644 --- a/media/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java @@ -24,7 +24,8 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; + static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN"; + static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; diff --git a/media/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 814bc72a2a78..41721f3011b1 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -20,9 +20,11 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; @@ -45,7 +47,7 @@ import java.util.concurrent.Executor; /** * Allows an app to interact with an active {@link MediaSession2} or a - * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other + * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other * commands can be sent to the session. * <p> * This API is not generally intended for third party application developers. @@ -53,7 +55,6 @@ import java.util.concurrent.Executor; * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. */ -// TODO: use @link for MediaSession2Service public class MediaController2 implements AutoCloseable { static final String TAG = "MediaController2"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -261,7 +262,8 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); + Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN); + Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); @@ -282,8 +284,7 @@ public class MediaController2 implements AutoCloseable { // Implementation for the local binder is no-op, // so can be used without worrying about deadlock. sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder); + mConnectedToken = token; } mCallbackExecutor.execute(() -> { mCallback.onConnected(MediaController2.this, allowedCommands); @@ -325,7 +326,7 @@ public class MediaController2 implements AutoCloseable { MediaController2.this, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -354,7 +355,7 @@ public class MediaController2 implements AutoCloseable { } private boolean requestConnectToSession() { - Session2Link sessionBinder = mSessionToken.getSessionLink(); + Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK); Bundle connectionRequest = createConnectionRequest(); try { sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); @@ -367,7 +368,7 @@ public class MediaController2 implements AutoCloseable { private boolean requestConnectToService() { // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE); + final Intent intent = new Intent(SESSION_SERVICE_INTERFACE); intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName()); // Use bindService() instead of startForegroundService() to start session service for three @@ -442,8 +443,8 @@ public class MediaController2 implements AutoCloseable { * @param controller the controller for this event * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, diff --git a/media/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java index c496cf75995e..c496cf75995e 100644 --- a/media/java/android/media/MediaItem2.java +++ b/media/apex/java/android/media/MediaItem2.java diff --git a/media/java/android/media/MediaMetadata.aidl b/media/apex/java/android/media/MediaMetadata.aidl index 66ee48304168..66ee48304168 100644 --- a/media/java/android/media/MediaMetadata.aidl +++ b/media/apex/java/android/media/MediaMetadata.aidl diff --git a/media/java/android/media/MediaMetadata.java b/media/apex/java/android/media/MediaMetadata.java index a3d75a30c2b7..dea98d591db1 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/apex/java/android/media/MediaMetadata.java @@ -250,16 +250,16 @@ public final class MediaMetadata implements Parcelable { * second line for media described by this metadata this should be preferred * to other fields if present. */ - public static final String METADATA_KEY_DISPLAY_SUBTITLE - = "android.media.metadata.DISPLAY_SUBTITLE"; + public static final String METADATA_KEY_DISPLAY_SUBTITLE = + "android.media.metadata.DISPLAY_SUBTITLE"; /** * A description that is suitable for display to the user. When displaying * more information for media described by this metadata this should be * preferred to other fields if present. */ - public static final String METADATA_KEY_DISPLAY_DESCRIPTION - = "android.media.metadata.DISPLAY_DESCRIPTION"; + public static final String METADATA_KEY_DISPLAY_DESCRIPTION = + "android.media.metadata.DISPLAY_DESCRIPTION"; /** * An icon or thumbnail that is suitable for display to the user. When @@ -270,8 +270,8 @@ public final class MediaMetadata implements Parcelable { * if it is too large. For higher resolution artwork * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead. */ - public static final String METADATA_KEY_DISPLAY_ICON - = "android.media.metadata.DISPLAY_ICON"; + public static final String METADATA_KEY_DISPLAY_ICON = + "android.media.metadata.DISPLAY_ICON"; /** * A Uri formatted String for an icon or thumbnail that is suitable for @@ -285,8 +285,8 @@ public final class MediaMetadata implements Parcelable { * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. */ - public static final String METADATA_KEY_DISPLAY_ICON_URI - = "android.media.metadata.DISPLAY_ICON_URI"; + public static final String METADATA_KEY_DISPLAY_ICON_URI = + "android.media.metadata.DISPLAY_ICON_URI"; /** * A String key for identifying the content. This value is specific to the @@ -320,8 +320,8 @@ public final class MediaMetadata implements Parcelable { * <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li> * </ul> */ - public static final String METADATA_KEY_BT_FOLDER_TYPE - = "android.media.metadata.BT_FOLDER_TYPE"; + public static final String METADATA_KEY_BT_FOLDER_TYPE = + "android.media.metadata.BT_FOLDER_TYPE"; private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = { METADATA_KEY_TITLE, diff --git a/media/java/android/media/MediaParceledListSlice.aidl b/media/apex/java/android/media/MediaParceledListSlice.aidl index 5c0e5bc84720..5c0e5bc84720 100644 --- a/media/java/android/media/MediaParceledListSlice.aidl +++ b/media/apex/java/android/media/MediaParceledListSlice.aidl diff --git a/media/java/android/media/MediaParceledListSlice.java b/media/apex/java/android/media/MediaParceledListSlice.java index 16a37d99fb86..16a37d99fb86 100644 --- a/media/java/android/media/MediaParceledListSlice.java +++ b/media/apex/java/android/media/MediaParceledListSlice.java diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java index aa79c417922c..a2feec2e9faf 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.MediaDrm.KeyRequest; import android.media.MediaPlayer2.DrmInfo; import android.media.MediaPlayer2Proto.PlayerMessage; import android.media.MediaPlayer2Proto.Value; @@ -76,6 +77,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -298,7 +301,7 @@ public class MediaPlayer2 implements AutoCloseable private volatile float mVolume = 1.0f; private VideoSize mVideoSize = new VideoSize(0, 0); - private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool(); + private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool(); // Creating a dummy audio track, used for keeping session id alive private final Object mSessionIdLock = new Object(); @@ -402,7 +405,7 @@ public class MediaPlayer2 implements AutoCloseable // Modular DRM clean up synchronized (mDrmEventCallbackLock) { - mDrmEventCallbackRecords.clear(); + mDrmEventCallback = null; } native_release(); @@ -2293,6 +2296,7 @@ public class MediaPlayer2 implements AutoCloseable private static final int MEDIA_PAUSED = 7; private static final int MEDIA_STOPPED = 8; private static final int MEDIA_SKIPPED = 9; + private static final int MEDIA_DRM_PREPARED = 10; private static final int MEDIA_NOTIFY_TIME = 98; private static final int MEDIA_TIMED_TEXT = 99; private static final int MEDIA_ERROR = 100; @@ -2330,7 +2334,16 @@ public class MediaPlayer2 implements AutoCloseable switch(msg.what) { case MEDIA_PREPARED: + case MEDIA_DRM_PREPARED: { + sourceInfo.mPrepareBarrier--; + if (sourceInfo.mPrepareBarrier > 0) { + break; + } else if (sourceInfo.mPrepareBarrier < 0) { + Log.w(TAG, "duplicated (drm) prepared events"); + break; + } + if (dsd != null) { sendEvent(new EventNotifier() { @Override @@ -2387,14 +2400,42 @@ public class MediaPlayer2 implements AutoCloseable } // notifying the client outside the lock + DrmPreparationInfo drmPrepareInfo = null; if (drmInfo != null) { - sendDrmEvent(new DrmEventNotifier() { + try { + drmPrepareInfo = sendDrmEventWait( + new DrmEventNotifier<DrmPreparationInfo>() { + @Override + public DrmPreparationInfo notifyWait( + DrmEventCallback callback) { + return callback.onDrmInfo(mMediaPlayer, dsd, + drmInfo); + } + }); + } catch (InterruptedException | ExecutionException + | TimeoutException e) { + Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e); + } + } + if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) { + sourceInfo.mPrepareBarrier++; + final Task prepareDrmTask; + prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID); + mTaskHandler.post(new Runnable() { @Override - public void notify(DrmEventCallback callback) { - callback.onDrmInfo( - mMediaPlayer, dsd, drmInfo); + public void run() { + // Run as simple Runnable, not Task + try { + prepareDrmTask.process(); + } catch (NoDrmSchemeException | IOException e) { + final String errMsg; + errMsg = "Unexpected Exception during prepareDrm"; + throw new RuntimeException(errMsg, e); + } } }); + } else { + Log.w(TAG, "No valid DrmPreparationInfo set"); } } else { Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); @@ -2892,7 +2933,8 @@ public class MediaPlayer2 implements AutoCloseable private void sendDrmEvent(final DrmEventNotifier notifier) { synchronized (mDrmEventCallbackLock) { try { - for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + Pair<Executor, DrmEventCallback> cb = mDrmEventCallback; + if (cb != null) { cb.first.execute(() -> notifier.notify(cb.second)); } } catch (RejectedExecutionException e) { @@ -2903,13 +2945,18 @@ public class MediaPlayer2 implements AutoCloseable } private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier) - throws InterruptedException, ExecutionException { + throws InterruptedException, ExecutionException, TimeoutException { + return sendDrmEventWait(notifier, 0); + } + + private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs) + throws InterruptedException, ExecutionException, TimeoutException { synchronized (mDrmEventCallbackLock) { - mDrmEventCallbackRecords.get(0); - for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + Pair<Executor, DrmEventCallback> cb = mDrmEventCallback; + if (cb != null) { CompletableFuture<T> ret = new CompletableFuture<>(); cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second))); - return ret.get(); + return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS); } } return null; @@ -3388,8 +3435,8 @@ public class MediaPlayer2 implements AutoCloseable private Map<String, String> mOptionalParameters; /** - * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from - * the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}. + * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved + * from the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}. * * @param uuid of selected crypto scheme * @return this @@ -3401,11 +3448,12 @@ public class MediaPlayer2 implements AutoCloseable /** * Set identifier of a persisted offline key obtained from - * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}. + * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}. * * A {@code keySetId} can be used to restore persisted offline keys into a new playback - * session of a DRM protected data source. When {@code keySetId} is set, {@code initData}, - * {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored. + * session of a DRM protected data source. When {@code keySetId} is set, + * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are + * ignored. * * @param keySetId identifier of a persisted offline key * @return this @@ -3455,24 +3503,24 @@ public class MediaPlayer2 implements AutoCloseable } /** - * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to - * the license server. + * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent + * to the license server. * * @param optionalParameters optional parameters to be included in a key request * @return this */ - public Builder setOptionalParameters( - @Nullable Map<String, String> optionalParameters) { + public Builder setOptionalParameters(@Nullable Map<String, String> optionalParameters) { this.mOptionalParameters = optionalParameters; return this; } /** - * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder + * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the + * settings of this builder */ public MediaPlayer2.DrmPreparationInfo build() { - return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType, - mOptionalParameters); + return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, + mKeyType, mOptionalParameters); } } @@ -3494,6 +3542,20 @@ public class MediaPlayer2 implements AutoCloseable this.mOptionalParameters = optionalParameters; } + boolean isValid() { + if (mUUID == null) { + return false; + } + if (mKeySetId != null) { + // offline restore case + return true; + } + if (mInitData != null && mMimeType != null) { + // new streaming license case + return true; + } + return false; + } } /** @@ -3501,6 +3563,7 @@ public class MediaPlayer2 implements AutoCloseable * DRM events. */ public static class DrmEventCallback { + /** * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that * bundles DRM initialization parameters. @@ -3517,21 +3580,6 @@ public class MediaPlayer2 implements AutoCloseable } /** - * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source - * {@code dsd} - * - * @param mp the {@code MediaPlayer2} associated with this callback - * @param dsd the {@link DataSourceDesc} of this data source - * @param status the result of DRM preparation. - * @param keySetId optional identifier that can be used to restore DRM playback initiated - * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request. - * - * @see DrmPreparationInfo.Builder#setKeySetId(byte[]) - */ - public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, - @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { } - - /** * Called to give the app the opportunity to configure DRM before the session is created. * * This facilitates configuration of the properties, like 'securityLevel', which @@ -3567,11 +3615,25 @@ public class MediaPlayer2 implements AutoCloseable return null; } + /** + * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source + * {@code dsd} or if there is an error during DRM preparation + * + * @param mp the {@code MediaPlayer2} associated with this callback + * @param dsd the {@link DataSourceDesc} of this data source + * @param status the result of DRM preparation. + * @param keySetId optional identifier that can be used to restore DRM playback initiated + * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request. + * + * @see DrmPreparationInfo.Builder#setKeySetId(byte[]) + */ + public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, + @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { } + } private final Object mDrmEventCallbackLock = new Object(); - private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords = - new ArrayList<Pair<Executor, DrmEventCallback>>(); + private Pair<Executor, DrmEventCallback> mDrmEventCallback; /** * Registers the callback to be invoked for various DRM events. @@ -3590,25 +3652,17 @@ public class MediaPlayer2 implements AutoCloseable "Illegal null Executor for the EventCallback"); } synchronized (mDrmEventCallbackLock) { - mDrmEventCallbackRecords = Collections.singletonList( - new Pair<Executor, DrmEventCallback>(executor, eventCallback)); + mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback); } } /** - * Unregisters the {@link DrmEventCallback}. - * - * @param eventCallback the callback to be unregistered - * @hide + * Clear the {@link DrmEventCallback}. */ // This is a synchronous call. - public void unregisterDrmEventCallback(DrmEventCallback eventCallback) { + public void clearDrmEventCallback() { synchronized (mDrmEventCallbackLock) { - for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { - if (cb.second == eventCallback) { - mDrmEventCallbackRecords.remove(cb); - } - } + mDrmEventCallback = null; } } @@ -3651,6 +3705,18 @@ public class MediaPlayer2 implements AutoCloseable */ public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5; + /** + * Restoring persisted offline keys failed. + * @hide + */ + public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6; + + /** + * Error during key request/response exchange with license server. + * @hide + */ + public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7; + /** @hide */ @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = { PREPARE_DRM_STATUS_SUCCESS, @@ -3659,6 +3725,8 @@ public class MediaPlayer2 implements AutoCloseable PREPARE_DRM_STATUS_PREPARATION_ERROR, PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, PREPARE_DRM_STATUS_RESOURCE_BUSY, + PREPARE_DRM_STATUS_RESTORE_ERROR, + PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, }) @Retention(RetentionPolicy.SOURCE) public @interface PrepareDrmStatusCode {} @@ -3747,12 +3815,16 @@ public class MediaPlayer2 implements AutoCloseable */ // This is an asynchronous call. public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) { - return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) { + return addTask(newPrepareDrmTask(dsd, uuid)); + } + + private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) { + return new Task(CALL_COMPLETED_PREPARE_DRM, true) { @Override void process() { final SourceInfo sourceInfo = getSourceInfo(dsd); int status = PREPARE_DRM_STATUS_PREPARATION_ERROR; - boolean sendEvent = true; + boolean finishPrepare = true; if (sourceInfo == null) { Log.e(TAG, "prepareDrm(): DataSource not found."); @@ -3780,8 +3852,8 @@ public class MediaPlayer2 implements AutoCloseable status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId); if (status == PREPARE_DRM_STATUS_SUCCESS) { - // DrmEventCallback will be fired in provisioning - sendEvent = false; + // License will be setup in provisioning + finishPrepare = false; } else { synchronized (sourceInfo.mDrmHandle) { sourceInfo.mDrmHandle.cleanDrmObj(); @@ -3808,23 +3880,16 @@ public class MediaPlayer2 implements AutoCloseable status = PREPARE_DRM_STATUS_PREPARATION_ERROR; } - if (sendEvent) { - final int prepareDrmStatus = status; - sendDrmEvent(new DrmEventNotifier() { - @Override - public void notify(DrmEventCallback callback) { - callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus, - /* TODO: keySetId */ null); - } - }); - + if (finishPrepare) { + sourceInfo.mDrmHandle.finishPrepare(status); synchronized (mTaskLock) { mCurrentTask = null; processPendingTask_l(); } } + } - }); + }; } /** @@ -4417,7 +4482,7 @@ public class MediaPlayer2 implements AutoCloseable }; // Modular DRM - final class DrmHandle { + private class DrmHandle { static final int PROVISION_TIMEOUT_MS = 60000; @@ -4432,6 +4497,7 @@ public class MediaPlayer2 implements AutoCloseable boolean mDrmProvisioningInProgress; boolean mPrepareDrmInProgress; Future<?> mProvisionResult; + DrmPreparationInfo mPrepareInfo; //--- guarded by |this| end DrmHandle(DataSourceDesc dsd, long srcId) { @@ -4441,7 +4507,7 @@ public class MediaPlayer2 implements AutoCloseable void prepare(UUID uuid) throws UnsupportedSchemeException, ResourceBusyException, NotProvisionedException, InterruptedException, - ExecutionException { + ExecutionException, TimeoutException { Log.v(TAG, "prepareDrm: uuid: " + uuid); synchronized (this) { @@ -4580,7 +4646,7 @@ public class MediaPlayer2 implements AutoCloseable // networking in a background thread mDrmProvisioningInProgress = true; - mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId)); + mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId)); return PREPARE_DRM_STATUS_SUCCESS; } @@ -4654,14 +4720,7 @@ public class MediaPlayer2 implements AutoCloseable } // synchronized // calling the callback outside the lock - final int finalStatus = status; - sendDrmEvent(new DrmEventNotifier() { - @Override - public void notify(DrmEventCallback callback) { - callback.onDrmPrepared( - MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null); - } - }); + finishPrepare(status); synchronized (mTaskLock) { if (mCurrentTask != null @@ -4703,6 +4762,93 @@ public class MediaPlayer2 implements AutoCloseable return success; } + synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) { + if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) { + return false; + } + mPrepareInfo = prepareInfo; + return true; + } + + void finishPrepare(int status) { + if (status != PREPARE_DRM_STATUS_SUCCESS) { + notifyPrepared(status, null); + return; + } + + if (mPrepareInfo == null) { + // Deprecated: this can only happen when using MediaPlayer Version 1 APIs + notifyPrepared(status, null); + return; + } + + final byte[] keySetId = mPrepareInfo.mKeySetId; + if (keySetId != null) { + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId); + } catch (Exception e) { + notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId); + } + return; + } + + sDrmThreadPool.submit(newKeyExchangeTask()); + } + + Runnable newKeyExchangeTask() { + return new Runnable() { + @Override + public void run() { + final byte[] initData = mPrepareInfo.mInitData; + final String mimeType = mPrepareInfo.mMimeType; + final int keyType = mPrepareInfo.mKeyType; + final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters; + byte[] keySetId = null; + try { + KeyRequest req; + req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams); + byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() { + @Override + public byte[] notifyWait(DrmEventCallback callback) { + final MediaPlayer2 mp = MediaPlayer2.this; + return callback.onDrmKeyRequest(mp, mDSD, req); + } + }); + keySetId = provideDrmKeyResponse(null, response); + } catch (Exception e) { + } + if (keySetId == null) { + notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null); + } else { + notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId); + } + } + }; + } + + void notifyPrepared(final int status, byte[] keySetId) { + + Message msg; + if (status == PREPARE_DRM_STATUS_SUCCESS) { + msg = mTaskHandler.obtainMessage( + MEDIA_DRM_PREPARED, 0, 0, null); + } else { + msg = mTaskHandler.obtainMessage( + MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null); + } + mTaskHandler.sendMessage(msg); + + sendDrmEvent(new DrmEventNotifier() { + @Override + public void notify(DrmEventCallback callback) { + callback.onDrmPrepared(MediaPlayer2.this, mDSD, status, + keySetId); + } + }); + + } + void cleanDrmObj() { // the caller holds mDrmLock Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); @@ -4768,6 +4914,7 @@ public class MediaPlayer2 implements AutoCloseable // set to false to avoid duplicate release calls this.mActiveDrmUUID = null; + native_releaseDrm(mSrcId); cleanDrmObj(); } // synchronized } @@ -4924,6 +5071,7 @@ public class MediaPlayer2 implements AutoCloseable final long mId = mSrcIdGenerator.getAndIncrement(); AtomicInteger mBufferedPercentage = new AtomicInteger(0); boolean mClosed = false; + int mPrepareBarrier = 1; // m*AsNextSource (below) only applies to pending data sources in the playlist; // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource} @@ -5022,7 +5170,7 @@ public class MediaPlayer2 implements AutoCloseable if (sourceInfo != null) { sourceInfo.close(); Runnable task = sourceInfo.mDrmHandle.newCleanupTask(); - mDrmThreadPool.submit(task); + sDrmThreadPool.submit(task); } } diff --git a/media/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java index 76ef27a40aad..80c91cc79c78 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/apex/java/android/media/MediaSession2.java @@ -20,10 +20,10 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.Bundle; import android.os.Handler; -import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; @@ -108,8 +107,10 @@ public class MediaSession2 implements AutoCloseable { mCallbackExecutor = callbackExecutor; mCallback = callback; mSessionStub = new Session2Link(this); - mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), - mSessionStub); + + Bundle extras = new Bundle(); + extras.putParcelable(KEY_SESSION2_LINK, mSessionStub); + mSessionToken = new Session2Token(context, id, extras); mSessionManager = (MediaSessionManager) mContext.getSystemService( Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. @@ -141,6 +142,8 @@ public class MediaSession2 implements AutoCloseable { for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } + mSessionToken.destroy(); + mSessionManager.notifySession2Destroyed(mSessionToken); } catch (Exception e) { // Should not be here. } @@ -328,7 +331,7 @@ public class MediaSession2 implements AutoCloseable { // It's needed because we cannot call synchronous calls between // session/controller. Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); + connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); @@ -397,7 +400,7 @@ public class MediaSession2 implements AutoCloseable { MediaSession2.this, controllerInfo, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -753,8 +756,8 @@ public class MediaSession2 implements AutoCloseable { * @param controller controller information * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session, diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java index 5bb746a7f9e3..f18cd317ef12 100644 --- a/media/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,10 +47,6 @@ import java.util.Map; * for consistent behavior across all devices. */ public abstract class MediaSession2Service extends Service { - /** - * The {@link Intent} that must be declared as handled by the service. - */ - public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -100,7 +98,7 @@ public abstract class MediaSession2Service extends Service { @Override @Nullable public IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { + if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } diff --git a/media/java/android/media/Session2Command.aidl b/media/apex/java/android/media/Session2Command.aidl index 43a7b123ed29..43a7b123ed29 100644 --- a/media/java/android/media/Session2Command.aidl +++ b/media/apex/java/android/media/Session2Command.aidl diff --git a/media/java/android/media/Session2Command.java b/media/apex/java/android/media/Session2Command.java index 8b285f212a8d..8b285f212a8d 100644 --- a/media/java/android/media/Session2Command.java +++ b/media/apex/java/android/media/Session2Command.java diff --git a/media/java/android/media/Session2CommandGroup.java b/media/apex/java/android/media/Session2CommandGroup.java index a189c264b029..2dab697c155d 100644 --- a/media/java/android/media/Session2CommandGroup.java +++ b/media/apex/java/android/media/Session2CommandGroup.java @@ -71,11 +71,10 @@ public final class Session2CommandGroup implements Parcelable { */ @SuppressWarnings("WeakerAccess") /* synthetic access */ Session2CommandGroup(Parcel in) { - Session2Command[] commands = in.readParcelableArray( - Session2Command.class.getClassLoader(), Session2Command.class); + Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader()); if (commands != null) { - for (Session2Command command : commands) { - mCommands.add(command); + for (Parcelable command : commands) { + mCommands.add((Session2Command) command); } } } diff --git a/media/java/android/media/Session2Link.java b/media/apex/java/android/media/Session2Link.java index 08664aa3b38f..08664aa3b38f 100644 --- a/media/java/android/media/Session2Link.java +++ b/media/apex/java/android/media/Session2Link.java diff --git a/media/java/android/media/Session2Token.aidl b/media/apex/java/android/media/Session2Token.aidl index c5980e9e77fd..c5980e9e77fd 100644 --- a/media/java/android/media/Session2Token.aidl +++ b/media/apex/java/android/media/Session2Token.aidl diff --git a/media/java/android/media/VolumeProvider.java b/media/apex/java/android/media/VolumeProvider.java index 1c017c564b43..49202eecef19 100644 --- a/media/java/android/media/VolumeProvider.java +++ b/media/apex/java/android/media/VolumeProvider.java @@ -16,6 +16,7 @@ package android.media; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.media.session.MediaSession; import java.lang.annotation.Retention; @@ -147,6 +148,7 @@ public abstract class VolumeProvider { * Sets a callback to receive volume changes. * @hide */ + @SystemApi public void setCallback(Callback callback) { mCallback = callback; } @@ -155,7 +157,11 @@ public abstract class VolumeProvider { * Listens for changes to the volume. * @hide */ - public static abstract class Callback { + @SystemApi + public abstract static class Callback { + /** + * Called when volume changed. + */ public abstract void onVolumeChanged(VolumeProvider volumeProvider); } } diff --git a/media/java/android/media/browse/MediaBrowser.aidl b/media/apex/java/android/media/browse/MediaBrowser.aidl index 782e09471a56..782e09471a56 100644 --- a/media/java/android/media/browse/MediaBrowser.aidl +++ b/media/apex/java/android/media/browse/MediaBrowser.aidl diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/apex/java/android/media/browse/MediaBrowser.java index b1b14c6e4ba5..2dffef9fb40a 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/apex/java/android/media/browse/MediaBrowser.java @@ -284,8 +284,8 @@ public final class MediaBrowser { */ public @NonNull ComponentName getServiceComponent() { if (!isConnected()) { - throw new IllegalStateException("getServiceComponent() called while not connected" + - " (state=" + mState + ")"); + throw new IllegalStateException("getServiceComponent() called while not connected" + + " (state=" + mState + ")"); } return mServiceComponent; } @@ -331,7 +331,7 @@ public final class MediaBrowser { * * @throws IllegalStateException if not connected. */ - public @NonNull MediaSession.Token getSessionToken() { + public @NonNull MediaSession.Token getSessionToken() { if (!isConnected()) { throw new IllegalStateException("getSessionToken() called while not connected (state=" + mState + ")"); @@ -464,7 +464,7 @@ public final class MediaBrowser { cb.onError(mediaId); return; } - cb.onItemLoaded((MediaItem)item); + cb.onItemLoaded((MediaItem) item); } }; try { @@ -575,7 +575,7 @@ public final class MediaBrowser { } } - private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback, + private void onServiceConnected(final IMediaBrowserServiceCallbacks callback, final String root, final MediaSession.Token session, final Bundle extra) { mHandler.post(new Runnable() { @Override @@ -625,7 +625,7 @@ public final class MediaBrowser { }); } - private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) { + private void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) { mHandler.post(new Runnable() { @Override public void run() { @@ -652,7 +652,7 @@ public final class MediaBrowser { }); } - private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback, + private void onLoadChildren(final IMediaBrowserServiceCallbacks callback, final String parentId, final MediaParceledListSlice list, final Bundle options) { mHandler.post(new Runnable() { @Override @@ -745,7 +745,7 @@ public final class MediaBrowser { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) + @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) public @interface Flags { } /** @@ -886,7 +886,7 @@ public final class MediaBrowser { /** * Callbacks for subscription related events. */ - public static abstract class SubscriptionCallback { + public abstract static class SubscriptionCallback { Binder mToken; public SubscriptionCallback() { @@ -947,7 +947,7 @@ public final class MediaBrowser { /** * Callback for receiving the result of {@link #getItem}. */ - public static abstract class ItemCallback { + public abstract static class ItemCallback { /** * Called when the item has been returned by the connected service. * @@ -1078,7 +1078,7 @@ public final class MediaBrowser { private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub { private WeakReference<MediaBrowser> mMediaBrowser; - public ServiceCallbacks(MediaBrowser mediaBrowser) { + ServiceCallbacks(MediaBrowser mediaBrowser) { mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser); } @@ -1125,7 +1125,7 @@ public final class MediaBrowser { private final List<SubscriptionCallback> mCallbacks; private final List<Bundle> mOptionsList; - public Subscription() { + Subscription() { mCallbacks = new ArrayList<>(); mOptionsList = new ArrayList<>(); } diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/apex/java/android/media/browse/MediaBrowserUtils.java index 2943e60dbbbd..19d9f008d3db 100644 --- a/media/java/android/media/browse/MediaBrowserUtils.java +++ b/media/apex/java/android/media/browse/MediaBrowserUtils.java @@ -22,6 +22,9 @@ import android.os.Bundle; * @hide */ public class MediaBrowserUtils { + /** + * Compares whether two bundles are the same. + */ public static boolean areSameOptions(Bundle options1, Bundle options2) { if (options1 == options2) { return true; @@ -39,6 +42,9 @@ public class MediaBrowserUtils { } } + /** + * Returnes true if the page options has duplicated items. + */ public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) { int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1); int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1); diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/apex/java/android/media/session/ControllerCallbackLink.aidl index 8ee8c7d00148..8ee8c7d00148 100644 --- a/media/java/android/media/session/ControllerCallbackLink.aidl +++ b/media/apex/java/android/media/session/ControllerCallbackLink.aidl diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/apex/java/android/media/session/ControllerCallbackLink.java index 28845e49a279..adc14a550b7d 100644 --- a/media/java/android/media/session/ControllerCallbackLink.java +++ b/media/apex/java/android/media/session/ControllerCallbackLink.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.media.MediaMetadata; +import android.media.MediaParceledListSlice; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession.QueueItem; import android.os.Binder; @@ -127,7 +128,8 @@ public final class ControllerCallbackLink implements Parcelable { @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueChanged(@Nullable List<QueueItem> queue) { try { - mIControllerCallback.notifyQueueChanged(queue); + mIControllerCallback.notifyQueueChanged(queue == null ? null : + new MediaParceledListSlice(queue)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -275,11 +277,11 @@ public final class ControllerCallbackLink implements Parcelable { } @Override - public void notifyQueueChanged(List<QueueItem> queue) { + public void notifyQueueChanged(MediaParceledListSlice queue) { ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { - mCallbackStub.onQueueChanged(queue); + mCallbackStub.onQueueChanged(queue == null ? null : queue.getList()); } finally { Binder.restoreCallingIdentity(token); } @@ -313,12 +315,6 @@ public final class ControllerCallbackLink implements Parcelable { } private void ensureMediaControlPermission() { - // Allow API calls from the System UI - if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) - == PackageManager.PERMISSION_GRANTED) { - return; - } - // Check if it's system server or has MEDIA_CONTENT_CONTROL. // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra // check here. diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/apex/java/android/media/session/ControllerLink.aidl index 532df59d16cf..532df59d16cf 100644 --- a/media/java/android/media/session/ControllerLink.aidl +++ b/media/apex/java/android/media/session/ControllerLink.aidl diff --git a/media/java/android/media/session/ControllerLink.java b/media/apex/java/android/media/session/ControllerLink.java index 937df20949f0..937df20949f0 100644 --- a/media/java/android/media/session/ControllerLink.java +++ b/media/apex/java/android/media/session/ControllerLink.java diff --git a/media/java/android/media/session/ISession.aidl b/media/apex/java/android/media/session/ISession.aidl index 9b1ad7bcf77c..9b1ad7bcf77c 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/apex/java/android/media/session/ISession.aidl diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/apex/java/android/media/session/ISessionCallback.aidl index 9b86bfced340..9b86bfced340 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/apex/java/android/media/session/ISessionCallback.aidl diff --git a/media/java/android/media/session/ISessionController.aidl b/media/apex/java/android/media/session/ISessionController.aidl index a3439a1a8deb..a3439a1a8deb 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/apex/java/android/media/session/ISessionController.aidl diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/apex/java/android/media/session/ISessionControllerCallback.aidl index 5c02e7c2f6e3..56ae852d6f50 100644 --- a/media/java/android/media/session/ISessionControllerCallback.aidl +++ b/media/apex/java/android/media/session/ISessionControllerCallback.aidl @@ -16,8 +16,8 @@ package android.media.session; import android.media.MediaMetadata; +import android.media.MediaParceledListSlice; import android.media.session.MediaController; -import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Bundle; @@ -31,7 +31,7 @@ oneway interface ISessionControllerCallback { // These callbacks are for the TransportController void notifyPlaybackStateChanged(in PlaybackState state); void notifyMetadataChanged(in MediaMetadata metadata); - void notifyQueueChanged(in List<MediaSession.QueueItem> queue); + void notifyQueueChanged(in MediaParceledListSlice queue); void notifyQueueTitleChanged(CharSequence title); void notifyExtrasChanged(in Bundle extras); void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info); diff --git a/media/java/android/media/session/MediaController.aidl b/media/apex/java/android/media/session/MediaController.aidl index 17167f45d0e3..17167f45d0e3 100644 --- a/media/java/android/media/session/MediaController.aidl +++ b/media/apex/java/android/media/session/MediaController.aidl diff --git a/media/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java index 057c9cb028c1..d43acf47b863 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/apex/java/android/media/session/MediaController.java @@ -206,6 +206,7 @@ public final class MediaController { } catch (RuntimeException e) { Log.wtf(TAG, "Error calling adjustVolumeBy", e); } + break; } case KeyEvent.ACTION_UP: { @@ -319,7 +320,7 @@ public final class MediaController { * * @return The current set of flags for the session. */ - public @MediaSession.SessionFlags long getFlags() { + public long getFlags() { try { return mSessionBinder.getFlags(); } catch (RuntimeException e) { @@ -582,7 +583,7 @@ public final class MediaController { return null; } - private final void postMessage(int what, Object obj, Bundle data) { + private void postMessage(int what, Object obj, Bundle data) { synchronized (mLock) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { mCallbacks.get(i).post(what, obj, data); @@ -594,7 +595,7 @@ public final class MediaController { * Callback for receiving updates from the session. A Callback can be * registered using {@link #registerCallback}. */ - public static abstract class Callback { + public abstract static class Callback { /** * Override to handle the session being destroyed. The session is no * longer valid after this call and calls to it will be ignored. @@ -1191,11 +1192,11 @@ public final class MediaController { } } - private final static class MessageHandler extends Handler { + private static final class MessageHandler extends Handler { private final MediaController.Callback mCallback; private boolean mRegistered = false; - public MessageHandler(Looper looper, MediaController.Callback cb) { + MessageHandler(Looper looper, MediaController.Callback cb) { super(looper); mCallback = cb; } diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java index c4634a915369..1f5fa5fe4127 100644 --- a/media/java/android/media/session/MediaSessionEngine.java +++ b/media/apex/java/android/media/session/MediaSessionEngine.java @@ -53,7 +53,7 @@ import java.util.Objects; */ @SystemApi public final class MediaSessionEngine implements AutoCloseable { - private static final String TAG = MediaSession.TAG; + private static final String TAG = "MediaSession"; private final Object mLock = new Object(); private final int mMaxBitmapSize; @@ -172,7 +172,7 @@ public final class MediaSessionEngine implements AutoCloseable { * * @param flags The flags to set for this session. */ - public void setFlags(@MediaSession.SessionFlags int flags) { + public void setFlags(int flags) { try { mSessionLink.setFlags(flags); } catch (RuntimeException e) { @@ -409,7 +409,7 @@ public final class MediaSessionEngine implements AutoCloseable { * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li> * </ul> */ - public void setRatingType(@Rating.Style int type) { + public void setRatingType(int type) { try { mSessionLink.setRatingType(type); } catch (RuntimeException e) { @@ -989,10 +989,8 @@ public final class MediaSessionEngine implements AutoCloseable { public static final class CallbackStub extends SessionCallbackLink.CallbackStub { private WeakReference<MediaSessionEngine> mSessionImpl; - private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - return new RemoteUserInfo(packageName, pid, uid, - caller != null ? caller.getBinder() : null); + private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) { + return new RemoteUserInfo(packageName, pid, uid); } public CallbackStub() { @@ -1003,7 +1001,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller), + sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid), command, args, cb); } } @@ -1015,7 +1013,7 @@ public final class MediaSessionEngine implements AutoCloseable { try { if (sessionImpl != null) { sessionImpl.dispatchMediaButton( - createRemoteUserInfo(packageName, pid, uid, null), mediaButtonIntent); + createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent); } } finally { if (cb != null) { @@ -1029,7 +1027,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller, Intent mediaButtonIntent) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller), + sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent); } } @@ -1039,7 +1037,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1050,7 +1048,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPrepareFromMediaId( - createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras); + createRemoteUserInfo(packageName, pid, uid), mediaId, extras); } } @@ -1061,7 +1059,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPrepareFromSearch( - createRemoteUserInfo(packageName, pid, uid, caller), query, extras); + createRemoteUserInfo(packageName, pid, uid), query, extras); } } @@ -1071,7 +1069,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPrepareFromUri( - createRemoteUserInfo(packageName, pid, uid, caller), uri, extras); + createRemoteUserInfo(packageName, pid, uid), uri, extras); } } @@ -1080,7 +1078,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1091,7 +1089,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPlayFromMediaId( - createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras); + createRemoteUserInfo(packageName, pid, uid), mediaId, extras); } } @@ -1102,7 +1100,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPlayFromSearch( - createRemoteUserInfo(packageName, pid, uid, caller), query, extras); + createRemoteUserInfo(packageName, pid, uid), query, extras); } } @@ -1112,7 +1110,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchPlayFromUri( - createRemoteUserInfo(packageName, pid, uid, caller), uri, extras); + createRemoteUserInfo(packageName, pid, uid), uri, extras); } } @@ -1122,7 +1120,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchSkipToItem( - createRemoteUserInfo(packageName, pid, uid, caller), id); + createRemoteUserInfo(packageName, pid, uid), id); } } @@ -1131,7 +1129,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1140,7 +1138,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1149,7 +1147,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1158,7 +1156,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1168,7 +1166,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchFastForward( - createRemoteUserInfo(packageName, pid, uid, caller)); + createRemoteUserInfo(packageName, pid, uid)); } } @@ -1177,7 +1175,7 @@ public final class MediaSessionEngine implements AutoCloseable { ControllerCallbackLink caller) { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { - sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller)); + sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid)); } } @@ -1187,7 +1185,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchSeekTo( - createRemoteUserInfo(packageName, pid, uid, caller), pos); + createRemoteUserInfo(packageName, pid, uid), pos); } } @@ -1197,7 +1195,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchRate( - createRemoteUserInfo(packageName, pid, uid, caller), rating); + createRemoteUserInfo(packageName, pid, uid), rating); } } @@ -1207,7 +1205,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchCustomAction( - createRemoteUserInfo(packageName, pid, uid, caller), action, args); + createRemoteUserInfo(packageName, pid, uid), action, args); } } @@ -1217,7 +1215,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchAdjustVolume( - createRemoteUserInfo(packageName, pid, uid, caller), direction); + createRemoteUserInfo(packageName, pid, uid), direction); } } @@ -1227,7 +1225,7 @@ public final class MediaSessionEngine implements AutoCloseable { MediaSessionEngine sessionImpl = mSessionImpl.get(); if (sessionImpl != null) { sessionImpl.dispatchSetVolumeTo( - createRemoteUserInfo(packageName, pid, uid, caller), value); + createRemoteUserInfo(packageName, pid, uid), value); } } diff --git a/media/java/android/media/session/MediaSessionProviderService.java b/media/apex/java/android/media/session/MediaSessionProviderService.java index 9a346ff4a12e..9a346ff4a12e 100644 --- a/media/java/android/media/session/MediaSessionProviderService.java +++ b/media/apex/java/android/media/session/MediaSessionProviderService.java diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/apex/java/android/media/session/PlaybackState.aidl index 0876ebd2d4d2..0876ebd2d4d2 100644 --- a/media/java/android/media/session/PlaybackState.aidl +++ b/media/apex/java/android/media/session/PlaybackState.aidl diff --git a/media/java/android/media/session/PlaybackState.java b/media/apex/java/android/media/session/PlaybackState.java index 2c57d1f676ed..6b28c976c710 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/apex/java/android/media/session/PlaybackState.java @@ -19,7 +19,6 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.Nullable; -import android.media.RemoteControlClient; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -42,7 +41,7 @@ public final class PlaybackState implements Parcelable { /** * @hide */ - @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, + @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING, ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH, ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE, @@ -192,42 +191,42 @@ public final class PlaybackState implements Parcelable { * @see Builder#setState(int, long, float) * @see Builder#setState(int, long, float, long) */ - public final static int STATE_NONE = 0; + public static final int STATE_NONE = 0; /** * State indicating this item is currently stopped. * * @see Builder#setState */ - public final static int STATE_STOPPED = 1; + public static final int STATE_STOPPED = 1; /** * State indicating this item is currently paused. * * @see Builder#setState */ - public final static int STATE_PAUSED = 2; + public static final int STATE_PAUSED = 2; /** * State indicating this item is currently playing. * * @see Builder#setState */ - public final static int STATE_PLAYING = 3; + public static final int STATE_PLAYING = 3; /** * State indicating this item is currently fast forwarding. * * @see Builder#setState */ - public final static int STATE_FAST_FORWARDING = 4; + public static final int STATE_FAST_FORWARDING = 4; /** * State indicating this item is currently rewinding. * * @see Builder#setState */ - public final static int STATE_REWINDING = 5; + public static final int STATE_REWINDING = 5; /** * State indicating this item is currently buffering and will begin playing @@ -235,7 +234,7 @@ public final class PlaybackState implements Parcelable { * * @see Builder#setState */ - public final static int STATE_BUFFERING = 6; + public static final int STATE_BUFFERING = 6; /** * State indicating this item is currently in an error state. The error @@ -243,7 +242,7 @@ public final class PlaybackState implements Parcelable { * * @see Builder#setState */ - public final static int STATE_ERROR = 7; + public static final int STATE_ERROR = 7; /** * State indicating the class doing playback is currently connecting to a @@ -253,21 +252,21 @@ public final class PlaybackState implements Parcelable { * * @see Builder#setState */ - public final static int STATE_CONNECTING = 8; + public static final int STATE_CONNECTING = 8; /** * State indicating the player is currently skipping to the previous item. * * @see Builder#setState */ - public final static int STATE_SKIPPING_TO_PREVIOUS = 9; + public static final int STATE_SKIPPING_TO_PREVIOUS = 9; /** * State indicating the player is currently skipping to the next item. * * @see Builder#setState */ - public final static int STATE_SKIPPING_TO_NEXT = 10; + public static final int STATE_SKIPPING_TO_NEXT = 10; /** * State indicating the player is currently skipping to a specific item in @@ -275,12 +274,12 @@ public final class PlaybackState implements Parcelable { * * @see Builder#setState */ - public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11; + public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; /** * Use this value for the position to indicate the position is not known. */ - public final static long PLAYBACK_POSITION_UNKNOWN = -1; + public static final long PLAYBACK_POSITION_UNKNOWN = -1; private final int mState; private final long mPosition; @@ -481,161 +480,6 @@ public final class PlaybackState implements Parcelable { return mExtras; } - /** - * Get the {@link PlaybackState} state for the given - * {@link RemoteControlClient} state. - * - * @param rccState The state used by {@link RemoteControlClient}. - * @return The equivalent state used by {@link PlaybackState}. - * @hide - */ - public static int getStateFromRccState(int rccState) { - switch (rccState) { - case RemoteControlClient.PLAYSTATE_BUFFERING: - return STATE_BUFFERING; - case RemoteControlClient.PLAYSTATE_ERROR: - return STATE_ERROR; - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return STATE_FAST_FORWARDING; - case RemoteControlClient.PLAYSTATE_NONE: - return STATE_NONE; - case RemoteControlClient.PLAYSTATE_PAUSED: - return STATE_PAUSED; - case RemoteControlClient.PLAYSTATE_PLAYING: - return STATE_PLAYING; - case RemoteControlClient.PLAYSTATE_REWINDING: - return STATE_REWINDING; - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return STATE_SKIPPING_TO_PREVIOUS; - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return STATE_SKIPPING_TO_NEXT; - case RemoteControlClient.PLAYSTATE_STOPPED: - return STATE_STOPPED; - default: - return -1; - } - } - - /** - * Get the {@link RemoteControlClient} state for the given - * {@link PlaybackState} state. - * - * @param state The state used by {@link PlaybackState}. - * @return The equivalent state used by {@link RemoteControlClient}. - * @hide - */ - public static int getRccStateFromState(int state) { - switch (state) { - case STATE_BUFFERING: - return RemoteControlClient.PLAYSTATE_BUFFERING; - case STATE_ERROR: - return RemoteControlClient.PLAYSTATE_ERROR; - case STATE_FAST_FORWARDING: - return RemoteControlClient.PLAYSTATE_FAST_FORWARDING; - case STATE_NONE: - return RemoteControlClient.PLAYSTATE_NONE; - case STATE_PAUSED: - return RemoteControlClient.PLAYSTATE_PAUSED; - case STATE_PLAYING: - return RemoteControlClient.PLAYSTATE_PLAYING; - case STATE_REWINDING: - return RemoteControlClient.PLAYSTATE_REWINDING; - case STATE_SKIPPING_TO_PREVIOUS: - return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS; - case STATE_SKIPPING_TO_NEXT: - return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS; - case STATE_STOPPED: - return RemoteControlClient.PLAYSTATE_STOPPED; - default: - return -1; - } - } - - /** - * @hide - */ - public static long getActionsFromRccControlFlags(int rccFlags) { - long actions = 0; - long flag = 1; - while (flag <= rccFlags) { - if ((flag & rccFlags) != 0) { - actions |= getActionForRccFlag((int) flag); - } - flag = flag << 1; - } - return actions; - } - - /** - * @hide - */ - public static int getRccControlFlagsFromActions(long actions) { - int rccFlags = 0; - long action = 1; - while (action <= actions && action < Integer.MAX_VALUE) { - if ((action & actions) != 0) { - rccFlags |= getRccFlagForAction(action); - } - action = action << 1; - } - return rccFlags; - } - - private static long getActionForRccFlag(int flag) { - switch (flag) { - case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS: - return ACTION_SKIP_TO_PREVIOUS; - case RemoteControlClient.FLAG_KEY_MEDIA_REWIND: - return ACTION_REWIND; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY: - return ACTION_PLAY; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE: - return ACTION_PLAY_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE: - return ACTION_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_STOP: - return ACTION_STOP; - case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD: - return ACTION_FAST_FORWARD; - case RemoteControlClient.FLAG_KEY_MEDIA_NEXT: - return ACTION_SKIP_TO_NEXT; - case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE: - return ACTION_SEEK_TO; - case RemoteControlClient.FLAG_KEY_MEDIA_RATING: - return ACTION_SET_RATING; - } - return 0; - } - - private static int getRccFlagForAction(long action) { - // We only care about the lower set of actions that can map to rcc - // flags. - int testAction = action < Integer.MAX_VALUE ? (int) action : 0; - switch (testAction) { - case (int) ACTION_SKIP_TO_PREVIOUS: - return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS; - case (int) ACTION_REWIND: - return RemoteControlClient.FLAG_KEY_MEDIA_REWIND; - case (int) ACTION_PLAY: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY; - case (int) ACTION_PLAY_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - case (int) ACTION_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE; - case (int) ACTION_STOP: - return RemoteControlClient.FLAG_KEY_MEDIA_STOP; - case (int) ACTION_FAST_FORWARD: - return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD; - case (int) ACTION_SKIP_TO_NEXT: - return RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - case (int) ACTION_SEEK_TO: - return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE; - case (int) ACTION_SET_RATING: - return RemoteControlClient.FLAG_KEY_MEDIA_RATING; - } - return 0; - } - public static final Parcelable.Creator<PlaybackState> CREATOR = new Parcelable.Creator<PlaybackState>() { @Override @@ -690,19 +534,19 @@ public final class PlaybackState implements Parcelable { return 0; } - public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR - = new Parcelable.Creator<PlaybackState.CustomAction>() { + public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR = + new Parcelable.Creator<PlaybackState.CustomAction>() { - @Override - public PlaybackState.CustomAction createFromParcel(Parcel p) { - return new PlaybackState.CustomAction(p); - } + @Override + public PlaybackState.CustomAction createFromParcel(Parcel p) { + return new PlaybackState.CustomAction(p); + } - @Override - public PlaybackState.CustomAction[] newArray(int size) { - return new PlaybackState.CustomAction[size]; - } - }; + @Override + public PlaybackState.CustomAction[] newArray(int size) { + return new PlaybackState.CustomAction[size]; + } + }; /** * Returns the action of the {@link CustomAction}. @@ -744,10 +588,7 @@ public final class PlaybackState implements Parcelable { @Override public String toString() { - return "Action:" + - "mName='" + mName + - ", mIcon=" + mIcon + - ", mExtras=" + mExtras; + return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras; } /** diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/apex/java/android/media/session/SessionCallbackLink.aidl index c489e5bee6e2..c489e5bee6e2 100644 --- a/media/java/android/media/session/SessionCallbackLink.aidl +++ b/media/apex/java/android/media/session/SessionCallbackLink.aidl diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/apex/java/android/media/session/SessionCallbackLink.java index 0dbf427b048d..3bcb65c42010 100644 --- a/media/java/android/media/session/SessionCallbackLink.java +++ b/media/apex/java/android/media/session/SessionCallbackLink.java @@ -944,12 +944,6 @@ public final class SessionCallbackLink implements Parcelable { } private void ensureMediaControlPermission() { - // Allow API calls from the System UI - if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) - == PackageManager.PERMISSION_GRANTED) { - return; - } - // Check if it's system server or has MEDIA_CONTENT_CONTROL. // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra // check here. diff --git a/media/java/android/media/session/SessionLink.aidl b/media/apex/java/android/media/session/SessionLink.aidl index c3be23e8f6b7..c3be23e8f6b7 100644 --- a/media/java/android/media/session/SessionLink.aidl +++ b/media/apex/java/android/media/session/SessionLink.aidl diff --git a/media/java/android/media/session/SessionLink.java b/media/apex/java/android/media/session/SessionLink.java index 0da0a5ae2fe1..4ea762367010 100644 --- a/media/java/android/media/session/SessionLink.java +++ b/media/apex/java/android/media/session/SessionLink.java @@ -23,8 +23,6 @@ import android.app.PendingIntent; import android.media.AudioAttributes; import android.media.MediaMetadata; import android.media.MediaParceledListSlice; -import android.media.Rating; -import android.media.VolumeProvider; import android.media.session.MediaSession.QueueItem; import android.os.Bundle; import android.os.IBinder; @@ -234,7 +232,7 @@ public final class SessionLink implements Parcelable { * * @param type the rating type. */ - void setRatingType(@Rating.Style int type) { + void setRatingType(int type) { try { mISession.setRatingType(type); } catch (RemoteException e) { @@ -261,7 +259,7 @@ public final class SessionLink implements Parcelable { * @param control the volume control type * @param max the max volume */ - void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) { + void setPlaybackToRemote(int control, int max) { try { mISession.setPlaybackToRemote(control, max); } catch (RemoteException e) { diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/apex/java/android/service/media/IMediaBrowserService.aidl index 84f41f6c3afe..1c50ec7ac421 100644 --- a/media/java/android/service/media/IMediaBrowserService.aidl +++ b/media/apex/java/android/service/media/IMediaBrowserService.aidl @@ -2,9 +2,7 @@ package android.service.media; -import android.content.res.Configuration; import android.service.media.IMediaBrowserServiceCallbacks; -import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl index 8dc480d6bff7..507a8f72ea57 100644 --- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl +++ b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl @@ -2,7 +2,6 @@ package android.service.media; -import android.graphics.Bitmap; import android.media.MediaParceledListSlice; import android.media.session.MediaSession; import android.os.Bundle; diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/apex/java/android/service/media/MediaBrowserService.java index d19d1177cae7..d9ef6ae40dfb 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/apex/java/android/service/media/MediaBrowserService.java @@ -98,7 +98,7 @@ public abstract class MediaBrowserService extends Service { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED, + @IntDef(flag = true, value = { RESULT_FLAG_OPTION_NOT_HANDLED, RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED }) private @interface ResultFlags { } @@ -291,7 +291,7 @@ public abstract class MediaBrowserService extends Service { final ConnectionRecord connection = mConnections.get(b); if (connection == null) { Log.w(TAG, "addSubscription for callback that isn't registered id=" - + id); + + id); return; } @@ -301,7 +301,8 @@ public abstract class MediaBrowserService extends Service { } @Override - public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) { + public void removeSubscriptionDeprecated( + String id, IMediaBrowserServiceCallbacks callbacks) { // do-nothing } @@ -487,7 +488,7 @@ public abstract class MediaBrowserService extends Service { @Override public void run() { Iterator<ConnectionRecord> iter = mConnections.values().iterator(); - while (iter.hasNext()){ + while (iter.hasNext()) { ConnectionRecord connection = iter.next(); try { connection.callbacks.onConnect(connection.root.getRootId(), token, @@ -541,8 +542,7 @@ public abstract class MediaBrowserService extends Service { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid, - mCurConnection.callbacks.asBinder()); + return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid); } /** @@ -608,7 +608,7 @@ public abstract class MediaBrowserService extends Service { final PackageManager pm = getPackageManager(); final String[] packages = pm.getPackagesForUid(uid); final int N = packages.length; - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { if (packages[i].equals(pkg)) { return true; } @@ -649,7 +649,7 @@ public abstract class MediaBrowserService extends Service { List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id); if (callbackList != null) { Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator(); - while (iter.hasNext()){ + while (iter.hasNext()) { if (token == iter.next().first) { removed = true; iter.remove(); @@ -820,8 +820,8 @@ public abstract class MediaBrowserService extends Service { */ public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED"; - final private String mRootId; - final private Bundle mExtras; + private final String mRootId; + private final Bundle mExtras; /** * Constructs a browser root. @@ -830,8 +830,8 @@ public abstract class MediaBrowserService extends Service { */ public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) { if (rootId == null) { - throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " + - "Use null for BrowserRoot instead."); + throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " + + "Use null for BrowserRoot instead."); } mRootId = rootId; mExtras = extras; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b7f042b48da1..f996d38c86a9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -30,6 +30,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -3989,33 +3990,11 @@ public class AudioManager { } /** - * Indicate A2DP source or sink connection state change. - * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) - * @param profile profile for the A2DP device - * (either {@link android.bluetooth.BluetoothProfile.A2DP} or - * {@link android.bluetooth.BluetoothProfile.A2DP_SINK}) - * @return a delay in ms that the caller should wait before broadcasting - * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent. - * {@hide} - */ - public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, - int profile) { - final IAudioService service = getService(); - int delay = 0; - try { - delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return delay; - } - - /** * Indicate A2DP source or sink connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) + * @param state new connection state, {@link BluetoothProfile#STATE_CONNECTED} + * or {@link BluetoothProfile#STATE_DISCONNECTED} * @param profile profile for the A2DP device * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting. * (either {@link android.bluetooth.BluetoothProfile.A2DP} or @@ -4027,8 +4006,8 @@ public class AudioManager { * {@hide} */ public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - BluetoothDevice device, int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { + BluetoothDevice device, int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { final IAudioService service = getService(); int delay = 0; try { diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index 52771e4199d8..1d763ced3ca0 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -356,11 +356,11 @@ public final class AudioRecordingConfiguration implements Parcelable { dest.writeInt(mDeviceSource); dest.writeInt(mClientEffects.length); for (int i = 0; i < mClientEffects.length; i++) { - mClientEffects[i].writeToParcel(dest, 0); + mClientEffects[i].writeToParcel(dest); } dest.writeInt(mDeviceEffects.length); for (int i = 0; i < mDeviceEffects.length; i++) { - mDeviceEffects[i].writeToParcel(dest, 0); + mDeviceEffects[i].writeToParcel(dest); } } @@ -375,13 +375,13 @@ public final class AudioRecordingConfiguration implements Parcelable { mClientPortId = in.readInt(); mClientSilenced = in.readBoolean(); mDeviceSource = in.readInt(); - mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); + mClientEffects = new AudioEffect.Descriptor[in.readInt()]; for (int i = 0; i < mClientEffects.length; i++) { - mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + mClientEffects[i] = new AudioEffect.Descriptor(in); } - mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); - for (int i = 0; i < mClientEffects.length; i++) { - mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + mDeviceEffects = new AudioEffect.Descriptor[in.readInt()]; + for (int i = 0; i < mDeviceEffects.length; i++) { + mDeviceEffects[i] = new AudioEffect.Descriptor(in); } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index af016d5d4be9..ffa3b247480a 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -39,6 +39,8 @@ import java.util.Map; */ public class AudioSystem { + private static final boolean DEBUG_VOLUME = true; + private static final String TAG = "AudioSystem"; /* These values must be kept in sync with system/audio.h */ /* @@ -879,6 +881,15 @@ public class AudioSystem } } + /** Wrapper for native methods called from AudioService */ + public static int setStreamVolumeIndexAS(int stream, int index, int device) { + if (DEBUG_VOLUME) { + Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream] + + " dev=" + Integer.toHexString(device) + " idx=" + index); + } + return setStreamVolumeIndex(stream, index, device); + } + // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t public static final int SYNC_EVENT_NONE = 0; public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; @@ -906,7 +917,7 @@ public class AudioSystem @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage - public static native int setStreamVolumeIndex(int stream, int index, int device); + private static native int setStreamVolumeIndex(int stream, int index, int device); public static native int getStreamVolumeIndex(int stream, int device); public static native int setMasterVolume(float value); public static native float getMasterVolume(); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 14bdab98d46b..f5aeca717424 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -151,8 +151,6 @@ interface IAudioService { void setWiredDeviceConnectionState(int type, int state, String address, String name, String caller); - int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile); - void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device, diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index bfc10da5d431..2d2c4a80bbf0 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -176,7 +176,8 @@ public final class MediaDrm implements AutoCloseable { * @param uuid The UUID of the crypto scheme. */ public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) { - return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null); + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null, + SECURITY_LEVEL_UNKNOWN); } /** @@ -189,7 +190,25 @@ public final class MediaDrm implements AutoCloseable { */ public static final boolean isCryptoSchemeSupported( @NonNull UUID uuid, @NonNull String mimeType) { - return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType); + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), + mimeType, SECURITY_LEVEL_UNKNOWN); + } + + /** + * Query if the given scheme identified by its UUID is supported on + * this device, and whether the DRM plugin is able to handle the + * media container format specified by mimeType at the requested + * security level. + * + * @param uuid The UUID of the crypto scheme. + * @param mimeType The MIME type of the media container, e.g. "video/mp4" + * or "video/webm" + * @param securityLevel the security level requested + */ + public static final boolean isCryptoSchemeSupported( + @NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) { + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType, + securityLevel); } private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { @@ -206,7 +225,7 @@ public final class MediaDrm implements AutoCloseable { } private static final native boolean isCryptoSchemeSupportedNative( - @NonNull byte[] uuid, @Nullable String mimeType); + @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel); private EventHandler createHandler() { Looper looper; diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 4eed12f428bc..33e7d8ea1b94 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -335,7 +335,7 @@ public class MediaScanner implements AutoCloseable { private final Uri mPlaylistsUri; @UnsupportedAppUsage private final Uri mFilesUri; - private final Uri mFilesUriNoNotify; + private final Uri mFilesFullUri; private final boolean mProcessPlaylists; private final boolean mProcessGenres; private int mMtpObjectHandle; @@ -445,7 +445,11 @@ public class MediaScanner implements AutoCloseable { mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); mFilesUri = Files.getContentUri(volumeName); - mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); + + Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); + filesFullUri = MediaStore.setIncludePending(filesFullUri); + filesFullUri = MediaStore.setIncludeTrashed(filesFullUri); + mFilesFullUri = filesFullUri; if (!volumeName.equals("internal")) { // we only support playlists on external media @@ -1625,7 +1629,7 @@ public class MediaScanner implements AutoCloseable { try { where = Files.FileColumns.DATA + "=?"; selectionArgs = new String[] { path }; - c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION, + c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION, where, selectionArgs, null, null); if (c != null && c.moveToFirst()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 3b51c82e06c9..325420b06122 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -21,18 +21,14 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; +import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; import android.media.session.PlaybackState; -import android.media.session.MediaSession; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.SystemClock; import android.util.Log; -import java.lang.IllegalArgumentException; - /** * RemoteControlClient enables exposing information meant to be consumed by remote controls * capable of displaying metadata, artwork and media transport control buttons. @@ -682,7 +678,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { - int pbState = PlaybackState.getStateFromRccState(state); + int pbState = getStateFromRccState(state); long position = hasPosition ? mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN; @@ -718,8 +714,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState); - bob.setActions( - PlaybackState.getActionsFromRccControlFlags(transportControlFlags)); + bob.setActions(getActionsFromRccControlFlags(transportControlFlags)); mSessionPlaybackState = bob.build(); mSession.setPlaybackState(mSessionPlaybackState); } @@ -1001,16 +996,19 @@ import java.lang.IllegalArgumentException; * Period for playback position drift checks, 15s when playing at 1x or slower. */ private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; + /** * Minimum period for playback position drift checks, never more often when every 2s, when * fast forwarding or rewinding. */ private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; + /** * The value above which the difference between client-reported playback position and * estimated position is considered a drift. */ private final static long POSITION_DRIFT_MAX_MS = 500; + /** * Compute the period at which the estimated playback position should be compared against the * actual playback position. Is a funciton of playback speed. @@ -1025,4 +1023,151 @@ import java.lang.IllegalArgumentException; POSITION_REFRESH_PERIOD_MIN_MS); } } + + /** + * Get the {@link PlaybackState} state for the given + * {@link RemoteControlClient} state. + * + * @param rccState The state used by {@link RemoteControlClient}. + * @return The equivalent state used by {@link PlaybackState}. + */ + private static int getStateFromRccState(int rccState) { + switch (rccState) { + case PLAYSTATE_BUFFERING: + return PlaybackState.STATE_BUFFERING; + case PLAYSTATE_ERROR: + return PlaybackState.STATE_ERROR; + case PLAYSTATE_FAST_FORWARDING: + return PlaybackState.STATE_FAST_FORWARDING; + case PLAYSTATE_NONE: + return PlaybackState.STATE_NONE; + case PLAYSTATE_PAUSED: + return PlaybackState.STATE_PAUSED; + case PLAYSTATE_PLAYING: + return PlaybackState.STATE_PLAYING; + case PLAYSTATE_REWINDING: + return PlaybackState.STATE_REWINDING; + case PLAYSTATE_SKIPPING_BACKWARDS: + return PlaybackState.STATE_SKIPPING_TO_PREVIOUS; + case PLAYSTATE_SKIPPING_FORWARDS: + return PlaybackState.STATE_SKIPPING_TO_NEXT; + case PLAYSTATE_STOPPED: + return PlaybackState.STATE_STOPPED; + default: + return -1; + } + } + + /** + * Get the {@link RemoteControlClient} state for the given + * {@link PlaybackState} state. + * + * @param state The state used by {@link PlaybackState}. + * @return The equivalent state used by {@link RemoteControlClient}. + */ + static int getRccStateFromState(int state) { + switch (state) { + case PlaybackState.STATE_BUFFERING: + return PLAYSTATE_BUFFERING; + case PlaybackState.STATE_ERROR: + return PLAYSTATE_ERROR; + case PlaybackState.STATE_FAST_FORWARDING: + return PLAYSTATE_FAST_FORWARDING; + case PlaybackState.STATE_NONE: + return PLAYSTATE_NONE; + case PlaybackState.STATE_PAUSED: + return PLAYSTATE_PAUSED; + case PlaybackState.STATE_PLAYING: + return PLAYSTATE_PLAYING; + case PlaybackState.STATE_REWINDING: + return PLAYSTATE_REWINDING; + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + return PLAYSTATE_SKIPPING_BACKWARDS; + case PlaybackState.STATE_SKIPPING_TO_NEXT: + return PLAYSTATE_SKIPPING_FORWARDS; + case PlaybackState.STATE_STOPPED: + return PLAYSTATE_STOPPED; + default: + return -1; + } + } + + private static long getActionsFromRccControlFlags(int rccFlags) { + long actions = 0; + long flag = 1; + while (flag <= rccFlags) { + if ((flag & rccFlags) != 0) { + actions |= getActionForRccFlag((int) flag); + } + flag = flag << 1; + } + return actions; + } + + static int getRccControlFlagsFromActions(long actions) { + int rccFlags = 0; + long action = 1; + while (action <= actions && action < Integer.MAX_VALUE) { + if ((action & actions) != 0) { + rccFlags |= getRccFlagForAction(action); + } + action = action << 1; + } + return rccFlags; + } + + private static long getActionForRccFlag(int flag) { + switch (flag) { + case FLAG_KEY_MEDIA_PREVIOUS: + return PlaybackState.ACTION_SKIP_TO_PREVIOUS; + case FLAG_KEY_MEDIA_REWIND: + return PlaybackState.ACTION_REWIND; + case FLAG_KEY_MEDIA_PLAY: + return PlaybackState.ACTION_PLAY; + case FLAG_KEY_MEDIA_PLAY_PAUSE: + return PlaybackState.ACTION_PLAY_PAUSE; + case FLAG_KEY_MEDIA_PAUSE: + return PlaybackState.ACTION_PAUSE; + case FLAG_KEY_MEDIA_STOP: + return PlaybackState.ACTION_STOP; + case FLAG_KEY_MEDIA_FAST_FORWARD: + return PlaybackState.ACTION_FAST_FORWARD; + case FLAG_KEY_MEDIA_NEXT: + return PlaybackState.ACTION_SKIP_TO_NEXT; + case FLAG_KEY_MEDIA_POSITION_UPDATE: + return PlaybackState.ACTION_SEEK_TO; + case FLAG_KEY_MEDIA_RATING: + return PlaybackState.ACTION_SET_RATING; + } + return 0; + } + + private static int getRccFlagForAction(long action) { + // We only care about the lower set of actions that can map to rcc + // flags. + int testAction = action < Integer.MAX_VALUE ? (int) action : 0; + switch (testAction) { + case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS: + return FLAG_KEY_MEDIA_PREVIOUS; + case (int) PlaybackState.ACTION_REWIND: + return FLAG_KEY_MEDIA_REWIND; + case (int) PlaybackState.ACTION_PLAY: + return FLAG_KEY_MEDIA_PLAY; + case (int) PlaybackState.ACTION_PLAY_PAUSE: + return FLAG_KEY_MEDIA_PLAY_PAUSE; + case (int) PlaybackState.ACTION_PAUSE: + return FLAG_KEY_MEDIA_PAUSE; + case (int) PlaybackState.ACTION_STOP: + return FLAG_KEY_MEDIA_STOP; + case (int) PlaybackState.ACTION_FAST_FORWARD: + return FLAG_KEY_MEDIA_FAST_FORWARD; + case (int) PlaybackState.ACTION_SKIP_TO_NEXT: + return FLAG_KEY_MEDIA_NEXT; + case (int) PlaybackState.ACTION_SEEK_TO: + return FLAG_KEY_MEDIA_POSITION_UPDATE; + case (int) PlaybackState.ACTION_SET_RATING: + return FLAG_KEY_MEDIA_RATING; + } + return 0; + } } diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index 5e9eed737256..f70963a982e4 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -632,8 +632,8 @@ import java.util.List; l = this.mOnClientUpdateListener; } if (l != null) { - int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState - .getRccStateFromState(state.getState()); + int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE + : RemoteControlClient.getRccStateFromState(state.getState()); if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) { l.onClientPlaybackStateUpdate(playstate); } else { @@ -642,7 +642,7 @@ import java.util.List; } if (state != null) { l.onClientTransportControlUpdate( - PlaybackState.getRccControlFlagsFromActions(state.getActions())); + RemoteControlClient.getRccControlFlagsFromActions(state.getActions())); } } } diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 42597aa0141c..73d3d889e464 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -138,7 +138,7 @@ public class Ringtone { mAudioAttributes = attributes; // The audio attributes have to be set before the media player is prepared. // Re-initialize it. - setUri(mUri); + setUri(mUri, mVolumeShaperConfig); } /** @@ -415,6 +415,7 @@ public class Ringtone { mLocalPlayer.reset(); mLocalPlayer.release(); mLocalPlayer = null; + mVolumeShaper = null; synchronized (sActiveRingtones) { sActiveRingtones.remove(this); } diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index 023ee4659b6c..80494ad1b2a6 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -19,13 +19,17 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.media.session.MediaSessionManager; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.text.TextUtils; import android.util.Log; @@ -35,7 +39,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a MediaSession2Service. + * Represents an ongoing MediaSession2 or a MediaSession2Service. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -44,7 +48,7 @@ import java.util.Objects; * for consistent behavior across all devices. * <p> * This may be passed to apps by the session owner to allow them to create a - * {@link MediaController2} to communicate with the session. + * MediaController2 to communicate with the session. * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ @@ -64,6 +68,13 @@ public final class Session2Token implements Parcelable { }; /** + * The {@link Intent} that must be declared for the session service. + * @hide + */ + @SystemApi + public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + + /** * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -72,22 +83,26 @@ public final class Session2Token implements Parcelable { } /** - * Type for {@link MediaSession2}. + * Type for MediaSession2. */ public static final int TYPE_SESSION = 0; /** - * Type for {@link MediaSession2Service}. + * Type for MediaSession2Service. */ public static final int TYPE_SESSION_SERVICE = 1; + private final String mSessionId; + private final int mPid; private final int mUid; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; - private final Session2Link mSessionLink; private final ComponentName mComponentName; + private final Bundle mExtras; + + private boolean mDestroyed = false; /** * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. @@ -106,44 +121,67 @@ public final class Session2Token implements Parcelable { final PackageManager manager = context.getPackageManager(); final int uid = getUid(manager, serviceComponent.getPackageName()); - if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE, - serviceComponent)) { + if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) { Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service."); } + mSessionId = null; mComponentName = serviceComponent; mPackageName = serviceComponent.getPackageName(); mServiceName = serviceComponent.getClassName(); + mPid = -1; mUid = uid; mType = TYPE_SESSION_SERVICE; - mSessionLink = null; + mExtras = null; } - Session2Token(int uid, int type, String packageName, Session2Link sessionLink) { - mUid = uid; - mType = type; - mPackageName = packageName; + /** + * Constructor for the token with type {@link #TYPE_SESSION}. + * + * @param context The context. + * @param sessionId The ID of the session. Should be unique. + * @param extras The extras. + * @hide + */ + @SystemApi + public Session2Token(@NonNull Context context, @NonNull String sessionId, + @Nullable Bundle extras) { + if (sessionId == null) { + throw new IllegalArgumentException("sessionId shouldn't be null"); + } + if (context == null) { + throw new IllegalArgumentException("context shouldn't be null"); + } + mSessionId = sessionId; + mPid = Process.myPid(); + mUid = Process.myUid(); + mType = TYPE_SESSION; + mPackageName = context.getPackageName(); + mExtras = extras; mServiceName = null; mComponentName = null; - mSessionLink = sessionLink; } Session2Token(Parcel in) { + mSessionId = in.readString(); + mPid = in.readInt(); mUid = in.readInt(); mType = in.readInt(); mPackageName = in.readString(); mServiceName = in.readString(); - mSessionLink = in.readParcelable(null); mComponentName = ComponentName.unflattenFromString(in.readString()); + mExtras = in.readParcelable(null); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSessionId); + dest.writeInt(mPid); dest.writeInt(mUid); dest.writeInt(mType); dest.writeString(mPackageName); dest.writeString(mServiceName); - dest.writeParcelable(mSessionLink, flags); dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); + dest.writeParcelable(mExtras, flags); } @Override @@ -153,7 +191,7 @@ public final class Session2Token implements Parcelable { @Override public int hashCode() { - return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); + return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName); } @Override @@ -162,17 +200,27 @@ public final class Session2Token implements Parcelable { return false; } Session2Token other = (Session2Token) obj; - return mUid == other.mUid - && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName) + return TextUtils.equals(mSessionId, other.mSessionId) + && mPid == other.mPid + && mUid == other.mUid && mType == other.mType - && Objects.equals(mSessionLink, other.mSessionLink); + && TextUtils.equals(mPackageName, other.mPackageName) + && TextUtils.equals(mServiceName, other.mServiceName); } @Override public String toString() { return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; + + " service=" + mServiceName + "}"; + } + + /** + * @return pid of the session + * @hide + */ + @SystemApi + public int getPid() { + return mPid; } /** @@ -199,14 +247,6 @@ public final class Session2Token implements Parcelable { } /** - * @hide - * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}. - */ - public ComponentName getComponentName() { - return mComponentName; - } - - /** * @return type of the token * @see #TYPE_SESSION * @see #TYPE_SESSION_SERVICE @@ -216,10 +256,35 @@ public final class Session2Token implements Parcelable { } /** + * @return extras + * @hide + */ + @SystemApi + @NonNull + public Bundle getExtras() { + return mExtras == null ? new Bundle() : new Bundle(mExtras); + } + + /** + * Destroys this session token. After this method is called, + * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called + * with this token. + * + * @see MediaSessionManager#notifySession2Created(Session2Token) + * @hide + */ + @SystemApi + public void destroy() { + mDestroyed = true; + } + + /** + * @return whether this token is destroyed * @hide */ - public Session2Link getSessionLink() { - return mSessionLink; + @SystemApi + public boolean isDestroyed() { + return mDestroyed; } private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface, diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 52e9ae191f0c..5b4bbce91784 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -26,7 +26,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Parcel; -import android.os.Parcelable; import android.util.Log; import java.lang.ref.WeakReference; @@ -229,7 +228,7 @@ public class AudioEffect { * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects * enumeration. */ - public static final class Descriptor implements Parcelable { + public static class Descriptor { public Descriptor() { } @@ -294,7 +293,9 @@ public class AudioEffect { this.implementor = implementor; } - private Descriptor(Parcel in) { + /** @hide */ + @TestApi + public Descriptor(Parcel in) { type = UUID.fromString(in.readString()); uuid = UUID.fromString(in.readString()); connectMode = in.readString(); @@ -302,33 +303,14 @@ public class AudioEffect { implementor = in.readString(); } - public static final Parcelable.Creator<Descriptor> CREATOR = - new Parcelable.Creator<Descriptor>() { - /** - * Rebuilds a Descriptor previously stored with writeToParcel(). - * @param p Parcel object to read the Descriptor from - * @return a new Descriptor created from the data in the parcel - */ - public Descriptor createFromParcel(Parcel p) { - return new Descriptor(p); - } - public Descriptor[] newArray(int size) { - return new Descriptor[size]; - } - }; - @Override public int hashCode() { return Objects.hash(type, uuid, connectMode, name, implementor); } - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { + /** @hide */ + @TestApi + public void writeToParcel(Parcel dest) { dest.writeString(type.toString()); dest.writeString(uuid.toString()); dest.writeString(connectMode); diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index ed162504c553..fa6e03430315 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -37,6 +37,7 @@ interface ISessionManager { SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag, int userId); void notifySession2Created(in Session2Token sessionToken); + void notifySession2Destroyed(in Session2Token sessionToken); List<ControllerLink> getSessions(in ComponentName compName, int userId); List<Session2Token> getSession2Tokens(int userId); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index eda913e50cfa..1a185e982cf1 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -116,7 +116,7 @@ public final class MediaSession { private final MediaSessionEngine mImpl; - // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection. + // Do not change the name of mCallback. Support lib accesses this by using reflection. @UnsupportedAppUsage private Object mCallback; @@ -144,7 +144,7 @@ public final class MediaSession { SessionLink sessionLink = manager.createSession(cbLink, tag); mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub, context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize)); + android.R.dimen.config_mediaMetadataBitmapMaxSize)); } catch (RuntimeException e) { throw new RuntimeException("Remote error creating session.", e); } @@ -160,6 +160,7 @@ public final class MediaSession { * @param callback The callback object */ public void setCallback(@Nullable Callback callback) { + mCallback = callback == null ? null : new Object(); mImpl.setCallback(callback); } @@ -173,6 +174,7 @@ public final class MediaSession { * @param handler The handler that events should be posted on. */ public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { + mCallback = callback == null ? null : new Object(); mImpl.setCallback(callback, handler); } @@ -479,7 +481,7 @@ public final class MediaSession { * @hide */ @SystemApi - ControllerLink getControllerLink() { + public ControllerLink getControllerLink() { return mControllerLink; } @@ -723,7 +725,7 @@ public final class MediaSession { private QueueItem(Parcel in) { mImpl = new MediaSessionEngine.QueueItem(in); - mId = in.readLong(); + mId = mImpl.getQueueId(); } /** diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 3f4fbb9af9a7..cae4d1749287 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -26,9 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.IRemoteVolumeController; -import android.media.MediaSession2; import android.media.Session2Token; -import android.media.browse.MediaBrowser; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -37,6 +35,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.service.media.MediaBrowserService; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.view.KeyEvent; @@ -115,11 +114,11 @@ public final class MediaSessionManager { } /** - * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is * created. * <p> * Do not use this API directly, but create a new instance through the - * {@link MediaSession2.Builder} instead. + * MediaSession2.Builder instead. * * @param token newly created session2 token */ @@ -130,6 +129,9 @@ public final class MediaSessionManager { if (token.getType() != Session2Token.TYPE_SESSION) { throw new IllegalArgumentException("token's type should be TYPE_SESSION"); } + if (token.isDestroyed()) { + throw new IllegalArgumentException("token is already destroyed"); + } try { mService.notifySession2Created(token); } catch (RemoteException e) { @@ -138,6 +140,31 @@ public final class MediaSessionManager { } /** + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is + * destroyed. + * <p> + * Do not use this API directly, but close a session with MediaSession2#close() instead. + * + * @param token destroyed session2 token + */ + public void notifySession2Destroyed(@NonNull Session2Token token) { + if (token == null) { + throw new IllegalArgumentException("token shouldn't be null"); + } + if (token.getType() != Session2Token.TYPE_SESSION) { + throw new IllegalArgumentException("token's type should be TYPE_SESSION"); + } + if (!token.isDestroyed()) { + throw new IllegalArgumentException("token should have been destroyed"); + } + try { + mService.notifySession2Destroyed(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Get a list of controllers for all ongoing sessions. The controllers will * be provided in priority order with the most important controller at index * 0. @@ -192,7 +219,7 @@ public final class MediaSessionManager { * current user. * <p> * Although this API can be used without any restriction, each session owners can accept or - * reject your uses of {@link MediaSession2}. + * reject your uses of MediaSession2. * * @return A list of {@link Session2Token}. */ @@ -797,7 +824,6 @@ public final class MediaSessionManager { private final String mPackageName; private final int mPid; private final int mUid; - private final IBinder mCallerBinder; /** * Create a new remote user information. @@ -807,22 +833,9 @@ public final class MediaSessionManager { * @param uid The uid of the remote user */ public RemoteUserInfo(@NonNull String packageName, int pid, int uid) { - this(packageName, pid, uid, null); - } - - /** - * Create a new remote user information. - * - * @param packageName The package name of the remote user - * @param pid The pid of the remote user - * @param uid The uid of the remote user - * @param callerBinder The binder of the remote user. Can be {@code null}. - */ - public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) { mPackageName = packageName; mPid = pid; mUid = uid; - mCallerBinder = callerBinder; } /** @@ -847,13 +860,8 @@ public final class MediaSessionManager { } /** - * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're - * sent to the same controller (either {@link MediaController} or - * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they - * would be considered as different one. - * <p> - * If you only want to compare the caller's package, compare them with the - * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly. + * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal + * if and only if they have the same package name, same pid, and same uid. * * @param obj the reference object with which to compare. * @return {@code true} if equals, {@code false} otherwise @@ -867,8 +875,9 @@ public final class MediaSessionManager { return true; } RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj; - return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false - : mCallerBinder.equals(otherUserInfo.mCallerBinder); + return TextUtils.equals(mPackageName, otherUserInfo.mPackageName) + && mPid == otherUserInfo.mPid + && mUid == otherUserInfo.mUid; } @Override diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 48dbf555e546..852d2962ee66 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -101,7 +101,7 @@ cc_library_shared { "libhidlbase", "libhidlmemory", - "libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API. + "libmediametrics", "libbinder", // Used by JWakeLock and MediaMetrics. "libutils", // Have to use shared lib to make libandroid_runtime behave correctly. diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index a45aa90f5f19..417a427b6c7c 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -405,6 +405,11 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w nativeFormat, consumerUsage); return; } + + if (consumerUsage & GRALLOC_USAGE_PROTECTED) { + gbConsumer->setConsumerIsProtected(true); + } + ctx->setBufferConsumer(bufferConsumer); bufferConsumer->setName(consumerName); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 42c5b052d4aa..81fce8a8044b 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -542,14 +542,15 @@ void JDrm::disconnect() { // static -bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { +bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType, + DrmPlugin::SecurityLevel securityLevel) { sp<IDrm> drm = MakeDrm(); if (drm == NULL) { return false; } - return drm->isCryptoSchemeSupported(uuid, mimeType); + return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel); } status_t JDrm::initCheck() const { @@ -930,8 +931,30 @@ static void android_media_MediaDrm_native_setup( setDrm(env, thiz, drm); } +DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { + DrmPlugin::SecurityLevel level; + + if (jlevel == gSecurityLevels.kSecurityLevelMax) { + level = DrmPlugin::kSecurityLevelMax; + } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) { + level = DrmPlugin::kSecurityLevelSwSecureCrypto; + } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) { + level = DrmPlugin::kSecurityLevelSwSecureDecode; + } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) { + level = DrmPlugin::kSecurityLevelHwSecureCrypto; + } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) { + level = DrmPlugin::kSecurityLevelHwSecureDecode; + } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) { + level = DrmPlugin::kSecurityLevelHwSecureAll; + } else { + level = DrmPlugin::kSecurityLevelUnknown; + } + return level; +} + static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( - JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType) { + JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType, + jint jSecurityLevel) { if (uuidObj == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); @@ -952,8 +975,9 @@ static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( if (jmimeType != NULL) { mimeType = JStringToString8(env, jmimeType); } + DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel); - return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType); + return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel); } static jbyteArray android_media_MediaDrm_openSession( @@ -965,21 +989,8 @@ static jbyteArray android_media_MediaDrm_openSession( } Vector<uint8_t> sessionId; - DrmPlugin::SecurityLevel level; - - if (jlevel == gSecurityLevels.kSecurityLevelMax) { - level = DrmPlugin::kSecurityLevelMax; - } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) { - level = DrmPlugin::kSecurityLevelSwSecureCrypto; - } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) { - level = DrmPlugin::kSecurityLevelSwSecureDecode; - } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) { - level = DrmPlugin::kSecurityLevelHwSecureCrypto; - } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) { - level = DrmPlugin::kSecurityLevelHwSecureDecode; - } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) { - level = DrmPlugin::kSecurityLevelHwSecureAll; - } else { + DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel); + if (level == DrmPlugin::kSecurityLevelUnknown) { jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level"); return NULL; } @@ -1903,7 +1914,7 @@ static const JNINativeMethod gMethods[] = { { "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V", (void *)android_media_MediaDrm_native_setup }, - { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z", + { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, { "openSession", "(I)[B", diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h index b9356f30bae8..93388612efcf 100644 --- a/media/jni/android_media_MediaDrm.h +++ b/media/jni/android_media_MediaDrm.h @@ -37,7 +37,9 @@ public: }; struct JDrm : public BnDrmClient { - static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType); + static bool IsCryptoSchemeSupported(const uint8_t uuid[16], + const String8 &mimeType, + DrmPlugin::SecurityLevel level); JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName); diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp index 3ded8c260512..de60b085b87d 100644 --- a/media/jni/android_media_MediaMetricsJNI.cpp +++ b/media/jni/android_media_MediaMetricsJNI.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "MediaMetricsJNI" + #include <jni.h> #include <nativehelper/JNIHelp.h> @@ -21,7 +23,10 @@ #include <media/MediaAnalyticsItem.h> -// Copeid from core/jni/ (libandroid_runtime.so) +// This source file is compiled and linked into both: +// core/jni/ (libandroid_runtime.so) +// media/jni (libmedia2_jni.so) + namespace android { // place the attributes into a java PersistableBundle object @@ -29,7 +34,7 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); + ALOGE("can't find android/os/PersistableBundle"); return NULL; } // sometimes the caller provides one for us to fill @@ -86,5 +91,138 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i return mybundle; } +// convert the specified batch metrics attributes to a persistent bundle. +// The encoding of the byte array is specified in +// frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +// +// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +enum { kInt32 = 0, kInt64, kDouble, kRate, kCString}; + +jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) { + ALOGV("writeAttributes()"); + + if (buffer == NULL || length <= 0) { + ALOGW("bad parameters to writeAttributesToBundle()"); + return NULL; + } + + jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); + if (clazzBundle==NULL) { + ALOGE("can't find android/os/PersistableBundle"); + return NULL; + } + // sometimes the caller provides one for us to fill + if (mybundle == NULL) { + // create the bundle + jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); + mybundle = env->NewObject(clazzBundle, constructID); + if (mybundle == NULL) { + ALOGD("unable to create mybundle"); + return NULL; + } + } + + int left = length; + char *buf = buffer; + + // grab methods that we can invoke + jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); + jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); + jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); + jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); + + +#define _EXTRACT(size, val) \ + { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);} +#define _SKIP(size) \ + { if ((size) > left) goto badness; buf += (size); left -= (size);} + + int32_t bufsize; + _EXTRACT(sizeof(int32_t), bufsize); + if (bufsize != length) { + goto badness; + } + int32_t proto; + _EXTRACT(sizeof(int32_t), proto); + if (proto != 0) { + ALOGE("unsupported wire protocol %d", proto); + goto badness; + } + + int32_t count; + _EXTRACT(sizeof(int32_t), count); + + // iterate through my attributes + // -- get name, get type, get value, insert into bundle appropriately. + for (int i = 0 ; i < count; i++ ) { + // prop name len (int16) + int16_t keylen; + _EXTRACT(sizeof(int16_t), keylen); + if (keylen <= 0) goto badness; + // prop name itself + char *key = buf; + jstring keyName = env->NewStringUTF(buf); + _SKIP(keylen); + + // prop type (int8_t) + int8_t attrType; + _EXTRACT(sizeof(int8_t), attrType); + + int16_t attrSize; + _EXTRACT(sizeof(int16_t), attrSize); + + switch (attrType) { + case kInt32: + { + int32_t i32; + _EXTRACT(sizeof(int32_t), i32); + env->CallVoidMethod(mybundle, setIntID, + keyName, (jint) i32); + break; + } + case kInt64: + { + int64_t i64; + _EXTRACT(sizeof(int64_t), i64); + env->CallVoidMethod(mybundle, setLongID, + keyName, (jlong) i64); + break; + } + case kDouble: + { + double d64; + _EXTRACT(sizeof(double), d64); + env->CallVoidMethod(mybundle, setDoubleID, + keyName, (jdouble) d64); + break; + } + case kCString: + { + jstring value = env->NewStringUTF(buf); + env->CallVoidMethod(mybundle, setStringID, + keyName, value); + _SKIP(attrSize); + break; + } + default: + ALOGW("ignoring Attribute '%s' unknown type: %d", + key, attrType); + _SKIP(attrSize); + break; + } + } + + // should have consumed it all + if (left != 0) { + ALOGW("did not consume entire buffer; left(%d) != 0", left); + goto badness; + } + + return mybundle; + + badness: + return NULL; +} + }; // namespace android diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h index fd621ea7261d..a10780f5c5c3 100644 --- a/media/jni/android_media_MediaMetricsJNI.h +++ b/media/jni/android_media_MediaMetricsJNI.h @@ -27,6 +27,7 @@ namespace android { class MediaMetricsJNI { public: static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); + static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length); }; }; // namespace android diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 9b4e730cfb5e..306916121740 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -786,22 +786,17 @@ android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz) return 0; } - Parcel p; - int key = FOURCC('m','t','r','X'); - status_t status = mp->getParameter(key, &p); + char *buffer = NULL; + size_t length = 0; + status_t status = mp->getMetrics(&buffer, &length); if (status != OK) { ALOGD("getMetrics() failed: %d", status); return (jobject) NULL; } - p.setDataPosition(0); - MediaAnalyticsItem *item = new MediaAnalyticsItem; - item->readFromParcel(p); - jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); + jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length); - // housekeeping - delete item; - item = NULL; + free(buffer); return mybundle; } diff --git a/media/packages/MediaCore/Android.bp b/media/packages/MediaCore/Android.bp.bak index c7fd58bf933a..c7fd58bf933a 100644 --- a/media/packages/MediaCore/Android.bp +++ b/media/packages/MediaCore/Android.bp.bak diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 8b45af0c3450..51afbc7d91b0 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -231,6 +231,7 @@ LIBANDROID { ASurfaceTransaction_setBuffer; # introduced=29 ASurfaceTransaction_setBufferAlpha; # introduced=29 ASurfaceTransaction_setBufferTransparency; # introduced=29 + ASurfaceTransaction_setColor; # introduced=29 ASurfaceTransaction_setDamageRegion; # introduced=29 ASurfaceTransaction_setDesiredPresentTime; # introduced=29 ASurfaceTransaction_setGeometry; # introduced=29 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index f0100a9fd4f9..3156732ef02b 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -44,6 +44,57 @@ using Transaction = SurfaceComposerClient::Transaction; LOG_ALWAYS_FATAL_IF(!static_cast<const Rect&>(name).isValid(), \ "invalid arg passed as " #name " argument"); +static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) { + sp<SurfaceComposerClient> client = surfaceControl->getClient(); + sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); + bool isWideColorDisplay = false; + status_t err = client->isWideColorDisplay(display, &isWideColorDisplay); + if (err) { + ALOGE("unable to get wide color support"); + return false; + } + return isWideColorDisplay; +} + +static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) { + sp<SurfaceComposerClient> client = surfaceControl->getClient(); + sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); + + HdrCapabilities hdrCapabilities; + status_t err = client->getHdrCapabilities(display, &hdrCapabilities); + if (err) { + ALOGE("unable to get hdr capabilities"); + return false; + } + + return !hdrCapabilities.getSupportedHdrTypes().empty(); +} + +static bool isDataSpaceValid(const sp<SurfaceControl>& surfaceControl, ADataSpace dataSpace) { + static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN)); + static_assert(static_cast<int>(ADATASPACE_SCRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SCRGB_LINEAR)); + static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB)); + static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB)); + static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3)); + static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ)); + + switch (static_cast<android_dataspace_t>(dataSpace)) { + case HAL_DATASPACE_UNKNOWN: + case HAL_DATASPACE_V0_SRGB: + return true; + // These data space need wide gamut support. + case HAL_DATASPACE_V0_SCRGB_LINEAR: + case HAL_DATASPACE_V0_SCRGB: + case HAL_DATASPACE_DISPLAY_P3: + return getWideColorSupport(surfaceControl); + // These data space need HDR support. + case HAL_DATASPACE_BT2020_PQ: + return getHdrSupport(surfaceControl); + default: + return false; + } +} + Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) { return reinterpret_cast<Transaction*>(aSurfaceTransaction); } @@ -431,3 +482,24 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTr transaction->setHdrMetadata(surfaceControl, hdrMetadata); } + +void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + float r, float g, float b, float alpha, + ADataSpace dataspace) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + LOG_ALWAYS_FATAL_IF(!isDataSpaceValid(surfaceControl, dataspace), "invalid dataspace"); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + half3 color; + color.r = r; + color.g = g; + color.b = b; + + transaction->setColor(surfaceControl, color) + .setColorAlpha(surfaceControl, alpha) + .setColorDataspace(surfaceControl, static_cast<ui::Dataspace>(dataspace)); +} diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h index 0490e650a7a4..e31ce195214f 100644 --- a/native/webview/plat_support/draw_fn.h +++ b/native/webview/plat_support/draw_fn.h @@ -20,7 +20,8 @@ extern "C" { // android to chromium are versioned. // // 1 is Android Q. This matches kAwDrawGLInfoVersion version 3. -static const int kAwDrawFnVersion = 1; +// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams. +static const int kAwDrawFnVersion = 2; struct AwDrawFn_OnSyncParams { int version; @@ -64,6 +65,16 @@ struct AwDrawFn_DrawGLParams { // Input: current transformation matrix in surface pixels. // Uses the column-based OpenGL matrix format. float transform[16]; + + // Input: Color space parameters. + float transfer_function_g; + float transfer_function_a; + float transfer_function_b; + float transfer_function_c; + float transfer_function_d; + float transfer_function_e; + float transfer_function_f; + float color_space_toXYZD50[9]; }; struct AwDrawFn_InitVkParams { diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp index b97bbc311624..afe103a25043 100644 --- a/native/webview/plat_support/draw_functor.cpp +++ b/native/webview/plat_support/draw_functor.cpp @@ -55,6 +55,8 @@ void onDestroyed(int functor, void* data) { void draw_gl(int functor, void* data, const uirenderer::DrawGlInfo& draw_gl_params) { + float gabcdef[7]; + draw_gl_params.color_space_ptr->transferFn(gabcdef); AwDrawFn_DrawGLParams params = { .version = kAwDrawFnVersion, .clip_left = draw_gl_params.clipLeft, @@ -64,12 +66,24 @@ void draw_gl(int functor, void* data, .width = draw_gl_params.width, .height = draw_gl_params.height, .is_layer = draw_gl_params.isLayer, + .transfer_function_g = gabcdef[0], + .transfer_function_a = gabcdef[1], + .transfer_function_b = gabcdef[2], + .transfer_function_c = gabcdef[3], + .transfer_function_d = gabcdef[4], + .transfer_function_e = gabcdef[5], + .transfer_function_f = gabcdef[6], }; COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform), mismatched_transform_matrix_sizes); for (int i = 0; i < NELEM(params.transform); ++i) { params.transform[i] = draw_gl_params.transform[i]; } + COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3), + gamut_transform_size_mismatch); + draw_gl_params.color_space_ptr->toXYZD50( + reinterpret_cast<skcms_Matrix3x3*>(¶ms.color_space_toXYZD50)); + SupportData* support = static_cast<SupportData*>(data); support->callbacks.draw_gl(functor, support->data, ¶ms); } diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index 9b6ad38545b4..9064ebe80da1 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -81,5 +81,5 @@ android_app { "com.android.keyguard", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml index 052566d67c1b..e591ea90c112 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml @@ -65,8 +65,9 @@ <com.android.systemui.statusbar.car.CarFacetButton android:id="@+id/music_nav" style="@style/NavigationBarButton" + systemui:categories="android.intent.category.APP_MUSIC" systemui:icon="@drawable/car_ic_music" - systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end" + systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end" systemui:packages="com.android.car.media" systemui:selectedIcon="@drawable/car_ic_music_selected" systemui:useMoreIcon="false" diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index dbddf71d342c..b37c5e69df76 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -114,9 +114,16 @@ public class CarStatusBar extends StatusBar implements new DeviceProvisionedController.DeviceProvisionedListener() { @Override public void onDeviceProvisionedChanged() { - mDeviceIsProvisioned = - mDeviceProvisionedController.isDeviceProvisioned(); - restartNavBars(); + mHandler.post(() -> { + // on initial boot we are getting a call even though the value + // is the same so we are confirming the reset is needed + boolean deviceProvisioned = + mDeviceProvisionedController.isDeviceProvisioned(); + if (mDeviceIsProvisioned != deviceProvisioned) { + mDeviceIsProvisioned = deviceProvisioned; + restartNavBars(); + } + }); } }); } diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java index 39a1676b4ec7..406b44d64da2 100644 --- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java +++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java @@ -20,8 +20,11 @@ import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.provider.DeviceConfig; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -29,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; * Observes the settings for {@link Assistant}. */ final class AssistantSettings extends ContentObserver { + private static final String LOG_TAG = "AssistantSettings"; public static Factory FACTORY = AssistantSettings::createAndRegister; private static final boolean DEFAULT_GENERATE_REPLIES = true; private static final boolean DEFAULT_GENERATE_ACTIONS = true; @@ -39,19 +43,33 @@ final class AssistantSettings extends ContentObserver { private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI = Settings.Global.getUriFor( Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT); - private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI = - Settings.Global.getUriFor( - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS); private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL); - private static final String KEY_GENERATE_REPLIES = "generate_replies"; - private static final String KEY_GENERATE_ACTIONS = "generate_actions"; + /** + * Flag determining whether the Notification Assistant should generate replies for + * notifications. + * <p> + * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}. + */ + @VisibleForTesting + static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies"; + + /** + * Flag determining whether the Notification Assistant should generate contextual actions in + * notifications. + * <p> + * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}. + */ + @VisibleForTesting + static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions"; private final KeyValueListParser mParser = new KeyValueListParser(','); private final ContentResolver mResolver; private final int mUserId; + private final Handler mHandler; + @VisibleForTesting protected final Runnable mOnUpdateRunnable; @@ -65,6 +83,7 @@ final class AssistantSettings extends ContentObserver { private AssistantSettings(Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) { super(handler); + mHandler = handler; mResolver = resolver; mUserId = userId; mOnUpdateRunnable = onUpdateRunnable; @@ -75,6 +94,7 @@ final class AssistantSettings extends ContentObserver { AssistantSettings assistantSettings = new AssistantSettings(handler, resolver, userId, onUpdateRunnable); assistantSettings.register(); + assistantSettings.registerDeviceConfigs(); return assistantSettings; } @@ -91,13 +111,62 @@ final class AssistantSettings extends ContentObserver { mResolver.registerContentObserver( DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId); mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId); - mResolver.registerContentObserver( - SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId); // Update all uris on creation. update(null); } + private void registerDeviceConfigs() { + DeviceConfig.addOnPropertyChangedListener( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + this::postToHandler, + this::onDeviceConfigPropertyChanged); + + // Update the fields in this class from the current state of the device config. + updateFromDeviceConfigFlags(); + } + + private void postToHandler(Runnable r) { + this.mHandler.post(r); + } + + @VisibleForTesting + void onDeviceConfigPropertyChanged(String namespace, String name, String value) { + if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) { + Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: " + + namespace + " " + name + "=" + value); + return; + } + + updateFromDeviceConfigFlags(); + } + + private void updateFromDeviceConfigFlags() { + String generateRepliesFlag = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + KEY_GENERATE_REPLIES); + if (TextUtils.isEmpty(generateRepliesFlag)) { + mGenerateReplies = DEFAULT_GENERATE_REPLIES; + } else { + // parseBoolean returns false for everything that isn't 'true' so there's no need to + // sanitise the flag string here. + mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag); + } + + String generateActionsFlag = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + KEY_GENERATE_ACTIONS); + if (TextUtils.isEmpty(generateActionsFlag)) { + mGenerateActions = DEFAULT_GENERATE_ACTIONS; + } else { + // parseBoolean returns false for everything that isn't 'true' so there's no need to + // sanitise the flag string here. + mGenerateActions = Boolean.parseBoolean(generateActionsFlag); + } + + mOnUpdateRunnable.run(); + } + @Override public void onChange(boolean selfChange, Uri uri) { update(uri); @@ -114,15 +183,6 @@ final class AssistantSettings extends ContentObserver { mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, ChannelImpressions.DEFAULT_STREAK_LIMIT); } - if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) { - mParser.setString( - Settings.Global.getString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); - mGenerateReplies = - mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES); - mGenerateActions = - mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS); - } if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) { int mNewInterruptionModelInt = Settings.Secure.getInt( mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 0d528e7078f8..5acf4fbaa5cb 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -202,7 +202,7 @@ public class SmartActionsHelper { } TextClassifierEvent textClassifierEvent = createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) - .setEntityType(ConversationAction.TYPE_TEXT_REPLY) + .setEntityTypes(ConversationAction.TYPE_TEXT_REPLY) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } @@ -225,7 +225,7 @@ public class SmartActionsHelper { } TextClassifierEvent textClassifierEvent = createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) - .setEntityType(actionType) + .setEntityTypes(actionType) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } @@ -291,7 +291,7 @@ public class SmartActionsHelper { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages == null || messages.length == 0) { return Arrays.asList(new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) .build()); } @@ -310,7 +310,7 @@ public class SmartActionsHelper { break; } Person author = localUser != null && localUser.equals(senderPerson) - ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + ? ConversationActions.Message.PERSON_USER_SELF : senderPerson; extractMessages.push(new ConversationActions.Message.Builder(author) .setText(message.getText()) .setReferenceTime( diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java index fd23f2b78b42..51b723d0950c 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import android.content.ContentResolver; import android.os.Handler; import android.os.Looper; +import android.provider.DeviceConfig; import android.provider.Settings; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -62,9 +63,6 @@ public class AssistantSettingsTest { Settings.Global.putFloat(mResolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f); Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2); - Settings.Global.putString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, - "generate_replies=true,generate_actions=true"); Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); mAssistantSettings = AssistantSettings.createForTesting( @@ -73,56 +71,78 @@ public class AssistantSettingsTest { @Test public void testGenerateRepliesDisabled() { - Settings.Global.putString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, - "generate_replies=false"); - - // Notify for the settings values we updated. - mAssistantSettings.onChange(false, - Settings.Global.getUriFor( - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); - + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_REPLIES, + "false"); assertFalse(mAssistantSettings.mGenerateReplies); } @Test public void testGenerateRepliesEnabled() { - Settings.Global.putString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true"); - - // Notify for the settings values we updated. - mAssistantSettings.onChange(false, - Settings.Global.getUriFor( - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_REPLIES, + "true"); assertTrue(mAssistantSettings.mGenerateReplies); } @Test - public void testGenerateActionsDisabled() { - Settings.Global.putString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false"); + public void testGenerateRepliesEmptyFlag() { + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_REPLIES, + "false"); - // Notify for the settings values we updated. - mAssistantSettings.onChange(false, - Settings.Global.getUriFor( - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + assertFalse(mAssistantSettings.mGenerateReplies); + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_REPLIES, + ""); + + // Go back to the default value. assertTrue(mAssistantSettings.mGenerateReplies); } @Test + public void testGenerateActionsDisabled() { + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_ACTIONS, + "false"); + + assertFalse(mAssistantSettings.mGenerateActions); + } + + @Test public void testGenerateActionsEnabled() { - Settings.Global.putString(mResolver, - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true"); + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_ACTIONS, + "true"); - // Notify for the settings values we updated. - mAssistantSettings.onChange(false, - Settings.Global.getUriFor( - Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + assertTrue(mAssistantSettings.mGenerateActions); + } - assertTrue(mAssistantSettings.mGenerateReplies); + @Test + public void testGenerateActionsEmptyFlag() { + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_ACTIONS, + "false"); + + assertFalse(mAssistantSettings.mGenerateActions); + + mAssistantSettings.onDeviceConfigPropertyChanged( + DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT, + AssistantSettings.KEY_GENERATE_ACTIONS, + ""); + + // Go back to the default value. + assertTrue(mAssistantSettings.mGenerateActions); } @Test diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 707349b0fd15..7f8127aa43a8 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -154,7 +154,7 @@ public class SmartActionHelperTest { ConversationActions.Message secondMessage = messages.get(0); MessageSubject.assertThat(secondMessage).hasText("secondMessage"); MessageSubject.assertThat(secondMessage) - .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + .hasPerson(ConversationActions.Message.PERSON_USER_SELF); MessageSubject.assertThat(secondMessage) .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 2f7d599c68bf..a2da0a079550 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -24,7 +24,7 @@ java_library { ":services-networkstack-shared-srcs", ], static_libs: [ - "dhcp-packet-lib", + "services-netlink-lib", ] } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 7f8bb93ae023..5ab833bda66d 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -28,6 +28,7 @@ <!-- Launch captive portal app as specific user --> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.NETWORK_STACK" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 494395285f5b..50c4dfc8d700 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -16,10 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; @@ -35,6 +31,10 @@ import static com.android.internal.util.BitUtils.getUint16; import static com.android.internal.util.BitUtils.getUint32; import static com.android.internal.util.BitUtils.getUint8; import static com.android.internal.util.BitUtils.uint32; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -46,7 +46,7 @@ import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; -import android.net.ip.IpClientCallbacks; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; @@ -337,7 +337,7 @@ public class ApfFilter { private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; private final ApfCapabilities mApfCapabilities; - private final IpClientCallbacks mIpClientCallback; + private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; private final IpConnectivityLog mMetricsLog; @@ -378,7 +378,7 @@ public class ApfFilter { @VisibleForTesting ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) { mApfCapabilities = config.apfCapabilities; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; @@ -1420,7 +1420,7 @@ public class ApfFilter { * filtering using APF programs. */ public static ApfFilter maybeCreate(Context context, ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacks ipClientCallback) { + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) { if (context == null || config == null || ifParams == null) return null; ApfCapabilities apfCapabilities = config.apfCapabilities; if (apfCapabilities == null) return null; diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 87a1b5ea8b4d..87a1b5ea8b4d 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java index b2eb4e21b591..b2eb4e21b591 100644 --- a/services/net/java/android/net/dhcp/DhcpAckPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index 04ac9a301813..04ac9a301813 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java index 7ecdea7b89da..7ecdea7b89da 100644 --- a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java index 11f2b6118e24..11f2b6118e24 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java index 7a83466c6e05..7a83466c6e05 100644 --- a/services/net/java/android/net/dhcp/DhcpInformPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java index 1da0b7300559..1da0b7300559 100644 --- a/services/net/java/android/net/dhcp/DhcpNakPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java index 0eba77e4a682..0eba77e4a682 100644 --- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java index ce8b7e78d0f8..ce8b7e78d0f8 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java index 39583032c20d..39583032c20d 100644 --- a/services/net/java/android/net/dhcp/DhcpReleasePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java index 231d04576c28..231d04576c28 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java index 385dd52e4576..385dd52e4576 100644 --- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java +++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java diff --git a/services/net/java/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 233b86f5f8b2..ad7f85d0a30a 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -16,7 +16,11 @@ package android.net.ip; +import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; + +import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; import android.content.Context; import android.net.ConnectivityManager; @@ -30,15 +34,16 @@ import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.ProxyInfoParcelable; import android.net.RouteInfo; -import android.net.StaticIpConfiguration; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.dhcp.DhcpClient; +import android.net.ip.IIpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.shared.InitialConfiguration; +import android.net.shared.NetdService; +import android.net.shared.ProvisioningConfiguration; import android.net.util.InterfaceParams; -import android.net.util.NetdService; import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.INetworkManagementService; @@ -53,7 +58,6 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.IState; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; @@ -69,6 +73,7 @@ import java.net.InetAddress; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.function.Predicate; @@ -101,11 +106,13 @@ public class IpClient extends StateMachine { private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>(); - // If |args| is non-empty, assume it's a list of interface names for which - // we should print IpClient logs (filter out all others). - public static void dumpAllLogs(PrintWriter writer, String[] args) { + /** + * Dump all state machine and connectivity packet logs to the specified writer. + * @param skippedIfaces Interfaces for which logs should not be dumped. + */ + public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) { for (String ifname : sSmLogs.keySet()) { - if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue; + if (skippedIfaces.contains(ifname)) continue; writer.println(String.format("--- BEGIN %s ---", ifname)); @@ -127,19 +134,6 @@ public class IpClient extends StateMachine { } } - /** - * TODO: remove after migrating clients to use IpClientCallbacks directly - * @see IpClientCallbacks - */ - public static class Callback extends IpClientCallbacks {} - - /** - * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback - * @see IpClientUtil.WaitForProvisioningCallbacks - */ - public static class WaitForProvisioningCallback - extends IpClientUtil.WaitForProvisioningCallbacks {} - // Use a wrapper class to log in order to ensure complete and detailed // logging. This method is lighter weight than annotations/reflection // and has the following benefits: @@ -160,181 +154,134 @@ public class IpClient extends StateMachine { // once passed on to the callback they may be modified by another thread. // // TODO: Find an lighter weight approach. - private class LoggingCallbackWrapper extends IpClientCallbacks { + public static class IpClientCallbacksWrapper { private static final String PREFIX = "INVOKE "; - private final IpClientCallbacks mCallback; + private final IIpClientCallbacks mCallback; + private final SharedLog mLog; - LoggingCallbackWrapper(IpClientCallbacks callback) { - mCallback = (callback != null) ? callback : new IpClientCallbacks(); + @VisibleForTesting + protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) { + mCallback = callback; + mLog = log; } private void log(String msg) { mLog.log(PREFIX + msg); } - @Override + private void log(String msg, Throwable e) { + mLog.e(PREFIX + msg, e); + } + public void onPreDhcpAction() { log("onPreDhcpAction()"); - mCallback.onPreDhcpAction(); + try { + mCallback.onPreDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPreDhcpAction", e); + } } - @Override + public void onPostDhcpAction() { log("onPostDhcpAction()"); - mCallback.onPostDhcpAction(); + try { + mCallback.onPostDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPostDhcpAction", e); + } } - @Override + public void onNewDhcpResults(DhcpResults dhcpResults) { log("onNewDhcpResults({" + dhcpResults + "})"); - mCallback.onNewDhcpResults(dhcpResults); + try { + mCallback.onNewDhcpResults(toStableParcelable(dhcpResults)); + } catch (RemoteException e) { + log("Failed to call onNewDhcpResults", e); + } } - @Override + public void onProvisioningSuccess(LinkProperties newLp) { log("onProvisioningSuccess({" + newLp + "})"); - mCallback.onProvisioningSuccess(newLp); + try { + mCallback.onProvisioningSuccess(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningSuccess", e); + } } - @Override + public void onProvisioningFailure(LinkProperties newLp) { log("onProvisioningFailure({" + newLp + "})"); - mCallback.onProvisioningFailure(newLp); + try { + mCallback.onProvisioningFailure(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningFailure", e); + } } - @Override + public void onLinkPropertiesChange(LinkProperties newLp) { log("onLinkPropertiesChange({" + newLp + "})"); - mCallback.onLinkPropertiesChange(newLp); + try { + mCallback.onLinkPropertiesChange(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onLinkPropertiesChange", e); + } } - @Override + public void onReachabilityLost(String logMsg) { log("onReachabilityLost(" + logMsg + ")"); - mCallback.onReachabilityLost(logMsg); + try { + mCallback.onReachabilityLost(logMsg); + } catch (RemoteException e) { + log("Failed to call onReachabilityLost", e); + } } - @Override + public void onQuit() { log("onQuit()"); - mCallback.onQuit(); + try { + mCallback.onQuit(); + } catch (RemoteException e) { + log("Failed to call onQuit", e); + } } - @Override + public void installPacketFilter(byte[] filter) { log("installPacketFilter(byte[" + filter.length + "])"); - mCallback.installPacketFilter(filter); + try { + mCallback.installPacketFilter(filter); + } catch (RemoteException e) { + log("Failed to call installPacketFilter", e); + } } - @Override + public void startReadPacketFilter() { log("startReadPacketFilter()"); - mCallback.startReadPacketFilter(); + try { + mCallback.startReadPacketFilter(); + } catch (RemoteException e) { + log("Failed to call startReadPacketFilter", e); + } } - @Override + public void setFallbackMulticastFilter(boolean enabled) { log("setFallbackMulticastFilter(" + enabled + ")"); - mCallback.setFallbackMulticastFilter(enabled); + try { + mCallback.setFallbackMulticastFilter(enabled); + } catch (RemoteException e) { + log("Failed to call setFallbackMulticastFilter", e); + } } - @Override + public void setNeighborDiscoveryOffload(boolean enable) { log("setNeighborDiscoveryOffload(" + enable + ")"); - mCallback.setNeighborDiscoveryOffload(enable); - } - } - - /** - * TODO: remove after migrating clients to use the shared configuration class directly. - * @see android.net.shared.ProvisioningConfiguration - */ - public static class ProvisioningConfiguration - extends android.net.shared.ProvisioningConfiguration { - public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) { - super(other); - } - - /** - * @see android.net.shared.ProvisioningConfiguration.Builder - */ - public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder { - // Override all methods to have a return type matching this Builder - @Override - public Builder withoutIPv4() { - super.withoutIPv4(); - return this; - } - - @Override - public Builder withoutIPv6() { - super.withoutIPv6(); - return this; - } - - @Override - public Builder withoutMultinetworkPolicyTracker() { - super.withoutMultinetworkPolicyTracker(); - return this; - } - - @Override - public Builder withoutIpReachabilityMonitor() { - super.withoutIpReachabilityMonitor(); - return this; - } - - @Override - public Builder withPreDhcpAction() { - super.withPreDhcpAction(); - return this; - } - - @Override - public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - super.withPreDhcpAction(dhcpActionTimeoutMs); - return this; - } - - @Override - public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - super.withStaticConfiguration(staticConfig); - return this; - } - - @Override - public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - super.withApfCapabilities(apfCapabilities); - return this; - } - - @Override - public Builder withProvisioningTimeoutMs(int timeoutMs) { - super.withProvisioningTimeoutMs(timeoutMs); - return this; - } - - @Override - public Builder withRandomMacAddress() { - super.withRandomMacAddress(); - return this; - } - - @Override - public Builder withStableMacAddress() { - super.withStableMacAddress(); - return this; - } - - @Override - public Builder withNetwork(Network network) { - super.withNetwork(network); - return this; - } - - @Override - public Builder withDisplayName(String displayName) { - super.withDisplayName(displayName); - return this; - } - - @Override - public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); + try { + mCallback.setNeighborDiscoveryOffload(enable); + } catch (RemoteException e) { + log("Failed to call setNeighborDiscoveryOffload", e); } } } - public static final String DUMP_ARG = "ipclient"; public static final String DUMP_ARG_CONFIRM = "confirm"; private static final int CMD_TERMINATE_AFTER_STOP = 1; @@ -388,7 +335,7 @@ public class IpClient extends StateMachine { private final String mInterfaceName; private final String mClatInterfaceName; @VisibleForTesting - protected final IpClientCallbacks mCallback; + protected final IpClientCallbacksWrapper mCallback; private final Dependencies mDependencies; private final CountDownLatch mShutdownLatch; private final ConnectivityManager mCm; @@ -444,7 +391,7 @@ public class IpClient extends StateMachine { } } - public IpClient(Context context, String ifName, IpClientCallbacks callback) { + public IpClient(Context context, String ifName, IIpClientCallbacks callback) { this(context, ifName, callback, new Dependencies()); } @@ -452,7 +399,7 @@ public class IpClient extends StateMachine { * An expanded constructor, useful for dependency injection. * TODO: migrate all test users to mock IpClient directly and remove this ctor. */ - public IpClient(Context context, String ifName, IpClientCallbacks callback, + public IpClient(Context context, String ifName, IIpClientCallbacks callback, INetworkManagementService nwService) { this(context, ifName, callback, new Dependencies() { @Override @@ -463,7 +410,7 @@ public class IpClient extends StateMachine { } @VisibleForTesting - IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) { + IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) { super(IpClient.class.getSimpleName() + "." + ifName); Preconditions.checkNotNull(ifName); Preconditions.checkNotNull(callback); @@ -473,7 +420,6 @@ public class IpClient extends StateMachine { mContext = context; mInterfaceName = ifName; mClatInterfaceName = CLAT_PREFIX + ifName; - mCallback = new LoggingCallbackWrapper(callback); mDependencies = deps; mShutdownLatch = new CountDownLatch(1); mCm = mContext.getSystemService(ConnectivityManager.class); @@ -484,6 +430,7 @@ public class IpClient extends StateMachine { sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); mConnectivityPacketLog = sPktLogs.get(mInterfaceName); mMsgStateLogger = new MessageHandlingLogger(); + mCallback = new IpClientCallbacksWrapper(callback, mLog); // TODO: Consider creating, constructing, and passing in some kind of // InterfaceController.Dependencies class. @@ -561,45 +508,53 @@ public class IpClient extends StateMachine { class IpClientConnector extends IIpClient.Stub { @Override public void completedPreDhcpAction() { + checkNetworkStackCallingPermission(); IpClient.this.completedPreDhcpAction(); } @Override public void confirmConfiguration() { + checkNetworkStackCallingPermission(); IpClient.this.confirmConfiguration(); } @Override public void readPacketFilterComplete(byte[] data) { + checkNetworkStackCallingPermission(); IpClient.this.readPacketFilterComplete(data); } @Override public void shutdown() { + checkNetworkStackCallingPermission(); IpClient.this.shutdown(); } @Override public void startProvisioning(ProvisioningConfigurationParcelable req) { - IpClient.this.startProvisioning( - android.net.shared.ProvisioningConfiguration.fromStableParcelable(req)); + checkNetworkStackCallingPermission(); + IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req)); } @Override public void stop() { + checkNetworkStackCallingPermission(); IpClient.this.stop(); } @Override public void setTcpBufferSizes(String tcpBufferSizes) { + checkNetworkStackCallingPermission(); IpClient.this.setTcpBufferSizes(tcpBufferSizes); } @Override public void setHttpProxy(ProxyInfoParcelable proxyInfo) { + checkNetworkStackCallingPermission(); IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo)); } @Override public void setMulticastFilter(boolean enabled) { + checkNetworkStackCallingPermission(); IpClient.this.setMulticastFilter(enabled); } - // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys - public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) { - IpClient.this.dump(fd, pw, args); - } + } + + public String getInterfaceName() { + return mInterfaceName; } private void configureAndStartStateMachine() { @@ -645,25 +600,10 @@ public class IpClient extends StateMachine { sendMessage(CMD_TERMINATE_AFTER_STOP); } - // In order to avoid deadlock, this method MUST NOT be called on the - // IpClient instance's thread. This prohibition includes code executed by - // when methods on the passed-in IpClient.Callback instance are called. - public void awaitShutdown() { - try { - mShutdownLatch.await(); - } catch (InterruptedException e) { - mLog.e("Interrupted while awaiting shutdown: " + e); - } - } - - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - /** * Start provisioning with the provided parameters. */ - public void startProvisioning(android.net.shared.ProvisioningConfiguration req) { + public void startProvisioning(ProvisioningConfiguration req) { if (!req.isValid()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); return; @@ -680,17 +620,6 @@ public class IpClient extends StateMachine { sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); } - // TODO: Delete this. - public void startProvisioning(StaticIpConfiguration staticIpConfig) { - startProvisioning(buildProvisioningConfiguration() - .withStaticConfiguration(staticIpConfig) - .build()); - } - - public void startProvisioning() { - startProvisioning(new android.net.shared.ProvisioningConfiguration()); - } - /** * Stop this IpClient. * diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index eb993a4243a9..eb993a4243a9 100644 --- a/services/net/java/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java index 761db6822fb4..761db6822fb4 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java index ec833b07e806..08c3f60eff3d 100644 --- a/services/net/java/android/net/util/ConnectivityPacketSummary.java +++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java @@ -16,47 +16,46 @@ package android.net.util; -import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER; -import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN; -import static android.net.util.NetworkConstants.ARP_REPLY; -import static android.net.util.NetworkConstants.ARP_REQUEST; -import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT; -import static android.net.util.NetworkConstants.ETHER_ADDR_LEN; -import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_HEADER_LEN; -import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_TYPE_ARP; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6; -import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET; -import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; -import static android.net.util.NetworkConstants.IPV4_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK; -import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.IPV4_IHL_MASK; -import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV6_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV6_HEADER_LEN; -import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.UDP_HEADER_LEN; -import static android.net.util.NetworkConstants.asString; -import static android.net.util.NetworkConstants.asUint; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_UDP; +import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER; +import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN; +import static com.android.server.util.NetworkStackConstants.ARP_REPLY; +import static com.android.server.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT; +import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET; +import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN; + import android.net.MacAddress; import android.net.dhcp.DhcpPacket; @@ -412,4 +411,25 @@ public class ConnectivityPacketSummary { final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x"; return String.format(MAC48_FORMAT, printableBytes); } + + /** + * Convenience method to convert an int to a String. + */ + public static String asString(int i) { + return Integer.toString(i); + } + + /** + * Convenience method to read a byte as an unsigned int. + */ + public static int asUint(byte b) { + return (b & 0xff); + } + + /** + * Convenience method to read a short as an unsigned int. + */ + public static int asUint(short s) { + return (s & 0xffff); + } } diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java index 8bbf449f6374..8bbf449f6374 100644 --- a/services/net/java/android/net/util/FdEventsReader.java +++ b/packages/NetworkStack/src/android/net/util/FdEventsReader.java diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java index 4aec6b6753a6..4aec6b6753a6 100644 --- a/services/net/java/android/net/util/PacketReader.java +++ b/packages/NetworkStack/src/android/net/util/PacketReader.java diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index cca71e759bdd..4080ddf9e73f 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -39,6 +39,8 @@ import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.os.IBinder; @@ -50,7 +52,11 @@ import com.android.server.connectivity.NetworkMonitor; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; /** * Android service used to start the network stack when bound to via an intent. @@ -80,6 +86,8 @@ public class NetworkStackService extends Service { private static final int NUM_VALIDATION_LOG_LINES = 20; private final Context mContext; private final ConnectivityManager mCm; + @GuardedBy("mIpClients") + private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); private static final int MAX_VALIDATION_LOGS = 10; @GuardedBy("mValidationLogs") @@ -138,6 +146,24 @@ public class NetworkStackService extends Service { } @Override + public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + final IpClient ipClient = new IpClient(mContext, ifName, cb); + + synchronized (mIpClients) { + final Iterator<WeakReference<IpClient>> it = mIpClients.iterator(); + while (it.hasNext()) { + final IpClient ipc = it.next().get(); + if (ipc == null) { + it.remove(); + } + } + mIpClients.add(new WeakReference<>(ipClient)); + } + + cb.onIpClientCreated(ipClient.makeConnector()); + } + + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { checkDumpPermission(); @@ -145,6 +171,33 @@ public class NetworkStackService extends Service { pw.println("NetworkStack logs:"); mLog.dump(fd, pw, args); + // Dump full IpClient logs for non-GCed clients + pw.println(); + pw.println("Recently active IpClient logs:"); + final ArrayList<IpClient> ipClients = new ArrayList<>(); + final HashSet<String> dumpedIpClientIfaces = new HashSet<>(); + synchronized (mIpClients) { + for (WeakReference<IpClient> ipcRef : mIpClients) { + final IpClient ipc = ipcRef.get(); + if (ipc != null) { + ipClients.add(ipc); + } + } + } + + for (IpClient ipc : ipClients) { + pw.println(ipc.getName()); + pw.increaseIndent(); + ipc.dump(fd, pw, args); + pw.decreaseIndent(); + dumpedIpClientIfaces.add(ipc.getInterfaceName()); + } + + // State machine and connectivity metrics logs are kept for GCed IpClients + pw.println(); + pw.println("Other IpClient logs:"); + IpClient.dumpAllLogs(fout, dumpedIpClientIfaces); + pw.println(); pw.println("Validation logs (most recent first):"); synchronized (mValidationLogs) { diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index a3d7852c9f4a..6b31b82ec3cc 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -247,7 +247,6 @@ public class NetworkMonitor extends StateMachine { private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final ConnectivityManager mCm; - private final NetworkRequest mDefaultRequest; private final IpConnectivityLog mMetricsLog; private final Dependencies mDependencies; @@ -336,7 +335,6 @@ public class NetworkMonitor extends StateMachine { mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - mDefaultRequest = defaultRequest; // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); @@ -486,8 +484,7 @@ public class NetworkMonitor extends StateMachine { } private boolean isValidationRequired() { - return NetworkMonitorUtils.isValidationRequired( - mDefaultRequest.networkCapabilities, mNetworkCapabilities); + return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities); } diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java index bb5900c53e52..eedaf30c055f 100644 --- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -32,12 +32,103 @@ public final class NetworkStackConstants { public static final int IPV4_MAX_MTU = 65_535; /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc2464 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + public static final int ETHER_HEADER_LEN = 14; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_ADDR_LEN = 16; + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_ECHO_REPLY_TYPE = 129; + public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_MTU = 5; + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + + + /** * DHCP constants. * * See also: * - https://tools.ietf.org/html/rfc2131 */ public static final int INFINITE_LEASE = 0xffffffff; + public static final int DHCP4_CLIENT_PORT = 68; private NetworkStackConstants() { throw new UnsupportedOperationException("This class is not to be instantiated"); diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index bd7ff2a75703..45fa2dc2f383 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -16,9 +16,12 @@ android_test { name: "NetworkStackTests", + certificate: "platform", srcs: ["src/**/*.java"], + resource_dirs: ["res"], static_libs: [ "android-support-test", + "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "NetworkStackLib", "testables", @@ -26,10 +29,70 @@ android_test { libs: [ "android.test.runner", "android.test.base", + "android.test.mock", ], jni_libs: [ // For mockito extended "libdexmakerjvmtiagent", "libstaticjvmtiagent", - ] -}
\ No newline at end of file + // For ApfTest + "libartbase", + "libbacktrace", + "libbase", + "libbinder", + "libbinderthreadstate", + "libc++", + "libcrypto", + "libcutils", + "libdexfile", + "libhidl-gen-utils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "liblzma", + "libnativehelper", + "libnetworkstacktestsjni", + "libpackagelistparser", + "libpcre2", + "libprocessgroup", + "libselinux", + "libui", + "libutils", + "libvintf", + "libvndksupport", + "libtinyxml2", + "libunwindstack", + "libutilscallstack", + "libziparchive", + "libz", + "netd_aidl_interface-cpp", + ], +} + +cc_library_shared { + name: "libnetworkstacktestsjni", + srcs: [ + "jni/**/*.cpp" + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + include_dirs: [ + "hardware/google/apf", + ], + shared_libs: [ + "libbinder", + "liblog", + "libcutils", + "libnativehelper", + "netd_aidl_interface-cpp", + ], + static_libs: [ + "libapf", + "libpcap", + ], + +} diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml index 8b8474f57e28..9cb2c21cc399 100644 --- a/packages/NetworkStack/tests/AndroidManifest.xml +++ b/packages/NetworkStack/tests/AndroidManifest.xml @@ -15,6 +15,35 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.networkstack.tests"> + + <uses-permission android:name="android.permission.READ_LOGS" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.GET_DETAILED_TASKS" /> + <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp index 4222adf9e06b..4222adf9e06b 100644 --- a/tests/net/jni/apf_jni.cpp +++ b/packages/NetworkStack/tests/jni/apf_jni.cpp diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap Binary files differindex 963165f19f73..963165f19f73 100644 --- a/tests/net/res/raw/apf.pcap +++ b/packages/NetworkStack/tests/res/raw/apf.pcap diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap Binary files differindex 6f69c4add0f8..6f69c4add0f8 100644 --- a/tests/net/res/raw/apfPcap.pcap +++ b/packages/NetworkStack/tests/res/raw/apfPcap.pcap diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 3c3e7ce3b12a..f76e41217c2a 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -16,8 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; @@ -28,12 +26,14 @@ import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_STREAM; import static com.android.internal.util.BitUtils.bytesToBEInt; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; @@ -42,10 +42,14 @@ import android.net.LinkProperties; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.ip.IpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.RaEvent; import android.net.util.InterfaceParams; +import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.Parcelable; import android.os.SystemClock; @@ -57,8 +61,9 @@ import android.system.Os; import android.text.format.DateUtils; import android.util.Log; -import com.android.frameworks.tests.net.R; import com.android.internal.util.HexDump; +import com.android.server.networkstack.tests.R; +import com.android.server.util.NetworkStackConstants; import libcore.io.IoUtils; import libcore.io.Streams; @@ -100,7 +105,7 @@ public class ApfTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // Load up native shared library containing APF interpreter exposed via JNI. - System.loadLibrary("frameworksnettestsjni"); + System.loadLibrary("networkstacktestsjni"); } private static final String TAG = "ApfTest"; @@ -915,10 +920,14 @@ public class ApfTest { HexDump.toHexString(data, false), result); } - private class MockIpClientCallback extends IpClientCallbacks { + private class MockIpClientCallback extends IpClientCallbacksWrapper { private final ConditionVariable mGotApfProgram = new ConditionVariable(); private byte[] mLastApfProgram; + MockIpClientCallback() { + super(mock(IIpClientCallbacks.class), mock(SharedLog.class)); + } + @Override public void installPacketFilter(byte[] filter) { mLastApfProgram = filter; @@ -946,7 +955,7 @@ public class ApfTest { private final long mFixedTimeMs = SystemClock.elapsedRealtime(); public TestApfFilter(Context context, ApfConfiguration config, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception { super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log); } @@ -1075,8 +1084,8 @@ public class ApfTest { private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0}; // Helper to initialize a default apfFilter. - private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config) - throws Exception { + private ApfFilter setupApfFilter( + IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); @@ -1294,7 +1303,7 @@ public class ApfTest { // However, we should still let through all other ICMPv6 types. ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); - raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT); + raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); assertPass(ipClientCallback.getApfProgram(), raPacket.array()); // Now wake up from doze mode to ensure that we no longer drop the packets. diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java index 5d57cde22fb1..5d57cde22fb1 100644 --- a/tests/net/java/android/net/apf/Bpf2Apf.java +++ b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java index a592809618e6..a592809618e6 100644 --- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index 5110ce1830b3..4ae044dec20b 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -16,10 +16,13 @@ package android.net.ip; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; @@ -87,7 +90,7 @@ public class IpClientTest { @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private Resources mResources; - @Mock private IpClientCallbacks mCb; + @Mock private IIpClientCallbacks mCb; @Mock private AlarmManager mAlarm; @Mock private IpClient.Dependencies mDependecies; private MockContentResolver mContentResolver; @@ -100,9 +103,7 @@ public class IpClientTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); - when(mContext.getSystemServiceName(ConnectivityManager.class)) - .thenReturn(Context.CONNECTIVITY_SERVICE); - when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); + when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); @@ -209,7 +210,8 @@ public class IpClientTest { verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -254,13 +256,15 @@ public class IpClientTest { mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr])); LinkProperties want = linkproperties(links(addresses), routes(prefixes)); want.setInterfaceName(iface); - verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want)); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat( + lp -> fromStableParcelable(lp).equals(want))); ipc.shutdown(); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -492,11 +496,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertTrue(IpClient.all(list1, (x) -> false)); - assertFalse(IpClient.all(list2, (x) -> false)); - assertTrue(IpClient.all(list3, (x) -> true)); - assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.all(list1, (x) -> false)); + assertFalse(InitialConfiguration.all(list2, (x) -> false)); + assertTrue(InitialConfiguration.all(list3, (x) -> true)); + assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f')); } @Test @@ -506,11 +510,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertFalse(IpClient.any(list1, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f')); - assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list1, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f')); } @Test diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java index e3b5ddf6f4cf..e3b5ddf6f4cf 100644 --- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java index dfaf52a953c7..dfaf52a953c7 100644 --- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java index dced7435ee74..dced7435ee74 100644 --- a/tests/net/java/android/net/util/PacketReaderTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 74aaf3c26aba..93f6a94dcf49 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -47,6 +47,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import java.util.List; +import java.util.Set; /** * Utility class to host methods usable in adding a restricted padlock icon and showing admin @@ -325,7 +326,8 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { if (admin == null) { return null; } - if (dpm.getCrossProfileCalendarPackages().isEmpty()) { + final Set<String> packages = dpm.getCrossProfileCalendarPackages(); + if (packages != null && packages.isEmpty()) { return admin; } return null; diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index 7357fe63d9b0..42afb69a27f4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -16,6 +16,7 @@ package com.android.settingslib.applications; +import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; @@ -123,4 +124,12 @@ public class AppUtils { return null; } + /** + * Returns a boolean indicating whether the given package is a hidden system module + */ + public static boolean isHiddenSystemModule(Context context, String packageName) { + return ApplicationsState.getInstance((Application) context.getApplicationContext()) + .isHiddenModule(packageName); + } + } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index a936df2bf2eb..c9fbc7ba9f05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -29,6 +29,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.IPackageStatsObserver; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageStats; @@ -71,6 +72,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -95,9 +97,14 @@ public class ApplicationsState { static ApplicationsState sInstance; public static ApplicationsState getInstance(Application app) { + return getInstance(app, AppGlobals.getPackageManager()); + } + + @VisibleForTesting + static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) { synchronized (sLock) { if (sInstance == null) { - sInstance = new ApplicationsState(app); + sInstance = new ApplicationsState(app, iPackageManager); } return sInstance; } @@ -132,6 +139,7 @@ public class ApplicationsState { String mCurComputingSizePkg; int mCurComputingSizeUserId; boolean mSessionsChanged; + final HashSet<String> mHiddenModules = new HashSet<>(); // Temporary for dispatching session callbacks. Only touched by main thread. final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>(); @@ -172,11 +180,11 @@ public class ApplicationsState { FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER; - private ApplicationsState(Application app) { + private ApplicationsState(Application app, IPackageManager iPackageManager) { mContext = app; mPm = mContext.getPackageManager(); mDrawableFactory = IconDrawableFactory.newInstance(mContext); - mIpm = AppGlobals.getPackageManager(); + mIpm = iPackageManager; mUm = mContext.getSystemService(UserManager.class); mStats = mContext.getSystemService(StorageStatsManager.class); for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) { @@ -194,6 +202,13 @@ public class ApplicationsState { mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; + final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */); + for (ModuleInfo info : moduleInfos) { + if (info.isHidden()) { + mHiddenModules.add(info.getPackageName()); + } + } + /** * This is a trick to prevent the foreground thread from being delayed. * The problem is that Dalvik monitors are initially spin locks, to keep @@ -283,6 +298,10 @@ public class ApplicationsState { } mHaveDisabledApps = true; } + if (isHiddenModule(info.packageName)) { + mApplications.remove(i--); + continue; + } if (!mHaveInstantApps && AppUtils.isInstant(info)) { mHaveInstantApps = true; } @@ -314,10 +333,15 @@ public class ApplicationsState { public boolean haveDisabledApps() { return mHaveDisabledApps; } + public boolean haveInstantApps() { return mHaveInstantApps; } + boolean isHiddenModule(String packageName) { + return mHiddenModules.contains(packageName); + } + void doPauseIfNeededLocked() { if (!mResumed) { return; diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index d9578014e846..305a1ff97e71 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -22,7 +22,6 @@ import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; 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; @@ -35,14 +34,14 @@ import android.os.ServiceManager; import android.text.format.DateUtils; import android.util.Pair; +import androidx.annotation.VisibleForTesting; +import androidx.loader.content.AsyncTaskLoader; + import com.android.settingslib.NetworkPolicyEditor; import java.time.ZonedDateTime; import java.util.Iterator; -import androidx.annotation.VisibleForTesting; -import androidx.loader.content.AsyncTaskLoader; - /** * Loader for network data usage history. It returns a list of usage data per billing cycle. */ @@ -121,8 +120,7 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { long cycleEnd = historyEnd; while (cycleEnd > historyStart) { - final long cycleStart = Math.max( - historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4)); + final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } 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 ccec175aefad..a098ecc17b3d 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 @@ -18,7 +18,9 @@ package com.android.settingslib.applications; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; @@ -33,12 +35,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.UserHandle; +import android.text.TextUtils; import android.util.IconDrawableFactory; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -46,11 +52,11 @@ import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.ApplicationsState.Session; import com.android.settingslib.testutils.shadow.ShadowUserManager; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -78,6 +84,8 @@ public class ApplicationsStateRoboTest { /** Class under test */ private ApplicationsState mApplicationsState; + private Session mSession; + @Mock private Callbacks mCallbacks; @@ -85,6 +93,8 @@ public class ApplicationsStateRoboTest { private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor; @Mock private StorageStatsManager mStorageStatsManager; + @Mock + private IPackageManager mPackageManagerService; @Implements(value = IconDrawableFactory.class) public static class ShadowIconDrawableFactory { @@ -99,6 +109,11 @@ public class ApplicationsStateRoboTest { public static class ShadowPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { + // test installed modules, 2 regular, 2 hidden + private final String[] mModuleNames = { + "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"}; + private final List<ModuleInfo> mInstalledModules = new ArrayList<>(); + @Implementation protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) { ResolveInfo resolveInfo = new ResolveInfo(); @@ -109,6 +124,16 @@ public class ApplicationsStateRoboTest { return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo"); } + @Implementation + public List<ModuleInfo> getInstalledModules(int flags) { + if (mInstalledModules.isEmpty()) { + for (String moduleName : mModuleNames) { + mInstalledModules.add(createModuleInfo(moduleName)); + } + } + return mInstalledModules; + } + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) { List<ResolveInfo> resolveInfos = new ArrayList<>(); @@ -121,6 +146,15 @@ public class ApplicationsStateRoboTest { resolveInfos.add(resolveInfo); return resolveInfos; } + + private ModuleInfo createModuleInfo(String packageName) { + final ModuleInfo info = new ModuleInfo(); + info.setName(packageName); + info.setPackageName(packageName); + // will treat any app with package name that contains "hidden" as hidden module + info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden")); + return info; + } } @Before @@ -136,12 +170,28 @@ public class ApplicationsStateRoboTest { storageStats.codeBytes = 10; storageStats.dataBytes = 20; storageStats.cacheBytes = 30; - when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class), - anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats); + when(mStorageStatsManager.queryStatsForPackage(any(UUID.class), + anyString(), any(UserHandle.class))).thenReturn(storageStats); + + // Set up 3 installed apps, in which 1 is hidden module + final List<ApplicationInfo> infos = new ArrayList<>(); + infos.add(createApplicationInfo("test.package.1")); + infos.add(createApplicationInfo("test.hidden.module.2")); + infos.add(createApplicationInfo("test.package.3")); + when(mPackageManagerService.getInstalledApplications( + anyInt() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos)); ApplicationsState.sInstance = null; - mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application); + mApplicationsState = + ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService); mApplicationsState.clearEntries(); + + mSession = mApplicationsState.newSession(mCallbacks); + } + + @After + public void tearDown() { + mSession.onDestroy(); } private ApplicationInfo createApplicationInfo(String packageName) { @@ -187,12 +237,11 @@ public class ApplicationsStateRoboTest { @Test public void testDefaultSessionLoadsAll() { - Session session = mApplicationsState.newSession(mCallbacks); - session.onResume(); + mSession.onResume(); addApp(HOME_PACKAGE_NAME, 1); addApp(LAUNCHABLE_PACKAGE_NAME, 2); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -211,17 +260,15 @@ public class ApplicationsStateRoboTest { AppEntry launchableEntry = findAppEntry(appEntries, 2); assertThat(launchableEntry.hasLauncherEntry).isTrue(); assertThat(launchableEntry.launcherEntryEnabled).isTrue(); - session.onDestroy(); } @Test public void testCustomSessionLoadsIconsOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -232,17 +279,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.icon).isNotNull(); assertThat(launchableEntry.size).isEqualTo(-1); assertThat(launchableEntry.hasLauncherEntry).isFalse(); - session.onDestroy(); } @Test public void testCustomSessionLoadsSizesOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -253,17 +298,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.icon).isNull(); assertThat(launchableEntry.hasLauncherEntry).isFalse(); assertThat(launchableEntry.size).isGreaterThan(0L); - session.onDestroy(); } @Test public void testCustomSessionLoadsHomeOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); + mSession.onResume(); addApp(HOME_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -275,17 +318,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.hasLauncherEntry).isFalse(); assertThat(launchableEntry.size).isEqualTo(-1); assertThat(launchableEntry.isHomeApp).isTrue(); - session.onDestroy(); } @Test public void testCustomSessionLoadsLeanbackOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -298,6 +339,16 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.isHomeApp).isFalse(); assertThat(launchableEntry.hasLauncherEntry).isTrue(); assertThat(launchableEntry.launcherEntryEnabled).isTrue(); - session.onDestroy(); } + + @Test + public void onResume_shouldNotIncludeSystemHiddenModule() { + mSession.onResume(); + + final List<ApplicationInfo> mApplications = mApplicationsState.mApplications; + assertThat(mApplications).hasSize(2); + assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1"); + assertThat(mApplications.get(1).packageName).isEqualTo("test.package.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 2d8ea125a97e..b8a143a376fd 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 @@ -130,7 +130,8 @@ public class NetworkCycleDataLoaderTest { .thenReturn(networkHistory); final long now = System.currentTimeMillis(); final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4); - when(networkHistory.getStart()).thenReturn(fourWeeksAgo); + final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2); + when(networkHistory.getStart()).thenReturn(twoDaysAgo); when(networkHistory.getEnd()).thenReturn(now); mLoader.loadFourWeeksData(); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index de86789053e9..dbeee1c4b2ca 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -40,11 +40,8 @@ <bool name="def_wifi_display_on">false</bool> <bool name="def_install_non_market_apps">false</bool> <bool name="def_package_verifier_enable">true</bool> - <!-- Comma-separated list of location providers. - Network location is off by default because it requires - user opt-in via Setup Wizard or Settings. - --> - <string name="def_location_providers_allowed" translatable="false">gps</string> + <!-- Comma-separated list of location providers --> + <string name="def_location_providers_allowed" translatable="false">gps,network</string> <bool name="assisted_gps_enabled">true</bool> <bool name="def_netstats_enabled">true</bool> <bool name="def_usb_mass_storage_enabled">true</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index e843eb43a3a6..4f6a4ad94479 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -19,7 +19,6 @@ package com.android.providers.settings; import android.annotation.NonNull; import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Global; import android.providers.settings.GlobalSettingsProto; import android.providers.settings.SecureSettingsProto; import android.providers.settings.SettingProto; @@ -397,7 +396,7 @@ class SettingsProtoDumpUtil { p.end(certPinToken); dumpSetting(s, p, - Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, + Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED); dumpSetting(s, p, Settings.Global.COMPATIBILITY_MODE, @@ -699,6 +698,9 @@ class SettingsProtoDumpUtil { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES); dumpSetting(s, p, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, + GlobalSettingsProto.Gpu.ANGLE_WHITELIST); + dumpSetting(s, p, Settings.Global.GPU_DEBUG_LAYER_APP, GlobalSettingsProto.Gpu.DEBUG_LAYER_APP); dumpSetting(s, p, @@ -716,6 +718,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GUP_BLACKLIST, GlobalSettingsProto.Gpu.GUP_BLACKLIST); + dumpSetting(s, p, + Settings.Global.GAME_DRIVER_WHITELIST, + GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST); p.end(gpuToken); final long hdmiToken = p.start(GlobalSettingsProto.HDMI); @@ -737,7 +742,7 @@ class SettingsProtoDumpUtil { Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED); dumpSetting(s, p, - Global.HIDDEN_API_BLACKLIST_EXEMPTIONS, + Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS, GlobalSettingsProto.HIDDEN_API_BLACKLIST_EXEMPTIONS); final long inetCondToken = p.start(GlobalSettingsProto.INET_CONDITION); @@ -817,6 +822,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS, GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS); + dumpSetting(s, p, + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, + GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST); p.end(locationToken); final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE); @@ -832,6 +840,15 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.AUTOMATIC_POWER_SAVER_MODE, GlobalSettingsProto.LowPowerMode.AUTOMATIC_POWER_SAVER_MODE); + dumpSetting(s, p, + Settings.Global.LOW_POWER_MODE_STICKY, + GlobalSettingsProto.LowPowerMode.STICKY_ENABLED); + dumpSetting(s, p, + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, + GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_ENABLED); + dumpSetting(s, p, + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, + GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_LEVEL); p.end(lpmToken); dumpSetting(s, p, @@ -882,7 +899,7 @@ class SettingsProtoDumpUtil { p.end(multiSimToken); dumpSetting(s, p, - Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED, + Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED, GlobalSettingsProto.NATIVE_FLAGS_HEALTH_CHECK_ENABLED); final long netstatsToken = p.start(GlobalSettingsProto.NETSTATS); @@ -1109,9 +1126,6 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.POWER_MANAGER_CONSTANTS, GlobalSettingsProto.POWER_MANAGER_CONSTANTS); - dumpSetting(s, p, - Settings.Global.PRIV_APP_OOB_ENABLED, - GlobalSettingsProto.PRIV_APP_OOB_ENABLED); final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP); dumpSetting(s, p, @@ -1262,10 +1276,10 @@ class SettingsProtoDumpUtil { final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER); dumpSetting(s, p, - Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, + Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY, GlobalSettingsProto.SoundTrigger.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY); dumpSetting(s, p, - Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT, + Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT, GlobalSettingsProto.SoundTrigger.DETECTION_SERVICE_OP_TIMEOUT_MS); p.end(soundTriggerToken); @@ -1561,7 +1575,7 @@ class SettingsProtoDumpUtil { GlobalSettingsProto.ZRAM_ENABLED); dumpSetting(s, p, - Global.APP_OPS_CONSTANTS, + Settings.Global.APP_OPS_CONSTANTS, GlobalSettingsProto.APP_OPS_CONSTANTS); p.end(token); @@ -2377,6 +2391,14 @@ class SettingsProtoDumpUtil { Settings.Secure.SILENCE_GESTURE, SecureSettingsProto.SILENCE_GESTURE_ENABLED); + dumpSetting(s, p, + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES); + + dumpSetting(s, p, + Settings.Secure.AWARE_ENABLED, + SecureSettingsProto.AWARE_ENABLED); + // Please insert new settings using the same order as in SecureSettingsProto. p.end(token); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 5105ff46703d..445312168eba 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3232,7 +3232,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 172; + private static final int SETTINGS_VERSION = 173; private final int mUserId; @@ -4217,6 +4217,41 @@ public class SettingsProvider extends ContentProvider { currentVersion = 172; } + if (currentVersion == 172) { + // Version 172: Set the default value for Secure Settings: LOCATION_MODE + + final SettingsState secureSettings = getSecureSettingsLocked(userId); + + final Setting locationMode = secureSettings.getSettingLocked( + Secure.LOCATION_MODE); + + if (locationMode.isNull()) { + final Setting locationProvidersAllowed = secureSettings.getSettingLocked( + Secure.LOCATION_PROVIDERS_ALLOWED); + + String defLocationMode = Integer.toString( + !TextUtils.isEmpty(locationProvidersAllowed.getValue()) + ? Secure.LOCATION_MODE_HIGH_ACCURACY + : Secure.LOCATION_MODE_OFF); + secureSettings.insertSettingLocked( + Secure.LOCATION_MODE, defLocationMode, + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + + // also reset LOCATION_PROVIDERS_ALLOWED back to the default value - this + // setting is now only for debug/test purposes, and will likely be removed + // in a later release. LocationManagerService is responsible for adjusting + // these settings to the proper state. + + String defLocationProvidersAllowed = getContext().getResources().getString( + R.string.def_location_providers_allowed); + secureSettings.insertSettingLocked( + Secure.LOCATION_PROVIDERS_ALLOWED, defLocationProvidersAllowed, + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + } + + currentVersion = 173; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b903142c44c6..c3c3f25a9f03 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -166,6 +166,8 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <!-- Permission needed to wipe the device for Test Harness Mode --> + <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 2d7471d9aca5..a9ff21fef99c 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -1962,8 +1962,7 @@ public class BugreportProgressService extends Service { } @Override - public void onFinished(long durationMs, String title, String description) - throws RemoteException { + public void onFinished() throws RemoteException { // TODO(b/111441001): implement } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8be67d9a7a51..e0d178fb9a1e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -57,6 +57,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "dagger2-2.19", @@ -73,7 +74,7 @@ android_library { "com.android.keyguard", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } android_library { @@ -108,6 +109,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", @@ -127,7 +129,7 @@ android_library { "--extra-packages", "com.android.keyguard:com.android.systemui", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } android_app { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b4f2711ef9d2..3453e798c7ae 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -386,6 +386,15 @@ android:excludeFromRecents="true"> </activity> + <!-- started from UsbPortManager --> + <activity android:name=".usb.UsbContaminantActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@style/Theme.SystemUI.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + <!-- started from AdbDebuggingManager --> <activity android:name=".usb.UsbDebuggingActivity" android:permission="android.permission.MANAGE_DEBUGGING" diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png Binary files differnew file mode 100644 index 000000000000..c3a45e294e79 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png Binary files differnew file mode 100644 index 000000000000..e77c676bc13f --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md new file mode 100644 index 000000000000..a67b5e873b2e --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout.md @@ -0,0 +1,56 @@ +# Physics Animation Layout + +## Overview +**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively. + +An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen. + +## PhysicsAnimationController +PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events. + +### Configuration Methods + +The controller must override the following methods: + +```Set<ViewProperty> getAnimatedProperties()``` +Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations. + +```int getNextAnimationInChain(ViewProperty property, int index)``` +If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind. + +```float getOffsetForChainedPropertyAnimation(ViewProperty property)``` +Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc. + +```SpringForce getSpringForce(ViewProperty property)``` +Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed. + +### Animation Control Methods + +Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views. + +In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation. + +For example, moving the first child view to *(100, 200)*: + +``` +animateValueForChildAtIndex(TRANSLATION_X, 0, 100); +animateValueForChildAtIndex(TRANSLATION_Y, 0, 200); +``` + +This would use the physics animations constructed by the layout to spring the view to *(100, 200)*. + +If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```. + +## PhysicsAnimationLayout +The layout itself is a FrameLayout descendant with a few extra methods: + +```setController(PhysicsAnimationController controller)``` +Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods. + +```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)``` +Sets an end listener that is called when all animations on the given property have ended. + +```setMaxRenderedChildren(int max)``` +Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**. + +It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md new file mode 100644 index 000000000000..47354d45fa33 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-testing.md @@ -0,0 +1,11 @@ +# Physics Animation Testing +Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic. + +For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions. + +## Waiting for Animations to End +Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations. + +To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important. + +The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
\ No newline at end of file diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 0b1dab1c3bca..fc84332151ec 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -20,14 +20,14 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import java.util.ArrayList; - import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.annotations.DependsOn; import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; + +import java.util.ArrayList; @ProvidesInterface(action = NotificationMenuRowPlugin.ACTION, version = NotificationMenuRowPlugin.VERSION) @@ -149,6 +149,12 @@ public interface NotificationMenuRowPlugin extends Plugin { public boolean canBeDismissed(); /** + * Informs the menu whether dismiss gestures are left-to-right or right-to-left. + */ + default void setDismissRtl(boolean dismissRtl) { + } + + /** * Determines whether the menu should remain open given its current state, or snap closed. * @return true if the menu should remain open, false otherwise. */ diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index 204408cda81f..13186fc6437c 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -22,8 +22,8 @@ <com.android.systemui.bubbles.BadgedImageView android:id="@+id/bubble_image" - android:layout_width="@dimen/bubble_size" - android:layout_height="@dimen/bubble_size" + android:layout_width="@dimen/individual_bubble_size" + android:layout_height="@dimen/individual_bubble_size" android:padding="@dimen/bubble_view_padding" android:clipToPadding="false"/> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml new file mode 100644 index 000000000000..e6f2376ae76b --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="bottom|center" + android:clipChildren="false" +> + + <LinearLayout + android:layout_height="290dp" + android:layout_width="412dp" + android:gravity="bottom" + android:padding="0dp" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:orientation="vertical" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + + <Space android:layout_width="match_parent" android:layout_height="2dp" + android:layout_weight="1" /> + + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:orientation="horizontal" + android:layoutDirection="rtl" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml new file mode 100644 index 000000000000..0c11cd977256 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="72dp" + android:layout_height="72dp" + android:gravity="center" + android:orientation="vertical" + android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_side_margin" + android:paddingEnd="4dip" + android:paddingStart="4dip"> + + <ImageView + android:id="@*android:id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:scaleType="center" + android:alpha="?android:attr/primaryContentAlpha" + /> + + <TextView + android:id="@*android:id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:paddingTop="10dp" + android:gravity="center" + android:textSize="12sp" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + <TextView + android:id="@*android:id/status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:gravity="center" + android:textColor="?android:attr/textColorTertiary" + android:textAppearance="?android:attr/textAppearanceSmall" + /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_footer_carrier.xml b/packages/SystemUI/res/layout/qs_footer_carrier.xml new file mode 100644 index 000000000000..bd492b022e36 --- /dev/null +++ b/packages/SystemUI/res/layout/qs_footer_carrier.xml @@ -0,0 +1,49 @@ +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/linear_footer_carrier" + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="horizontal" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:background="@android:color/transparent" + android:clickable="false" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingStart="16dp" > + + <include + layout="@layout/mobile_signal_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:visibility="gone" /> + + <view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText" + android:id="@+id/qs_carrier_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="marquee" + android:textAppearance="@style/TextAppearance.QS.CarrierInfo" + android:textColor="?android:attr/textColorPrimary" + android:textDirection="locale" + android:singleLine="true" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 890bf5d8ac45..abf9e056ed22 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -42,30 +42,31 @@ android:gravity="end" > <LinearLayout + android:id="@+id/qs_mobile" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical|start" - android:paddingStart="16dp"> + android:orientation="horizontal" + android:layout_marginEnd="32dp"> <include - layout="@layout/mobile_signal_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" + layout="@layout/qs_footer_carrier" + android:id="@+id/carrier1" /> + + <View + android:id="@+id/qs_carrier_divider" + android:layout_width="2dp" + android:layout_height="match_parent" + android:layout_marginTop="15dp" + android:layout_marginBottom="15dp" + android:background="?android:attr/dividerVertical" android:visibility="gone" /> - <com.android.keyguard.CarrierText - android:id="@+id/qs_carrier_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginEnd="32dp" - android:ellipsize="marquee" - android:textAppearance="@style/TextAppearance.QS.CarrierInfo" - android:textColor="?android:attr/textColorPrimary" - android:textDirection="locale" - android:singleLine="true" /> + <include + layout="@layout/qs_footer_carrier" + android:id="@+id/carrier2" + android:visibility="gone"/> </LinearLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ab0bbe10c37c..a14259eca45e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -834,6 +834,18 @@ <dimen name="global_actions_panel_width">120dp</dimen> + <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen> + + <dimen name="global_actions_grid_side_margin">4dp</dimen> + <dimen name="global_actions_grid_separated_panel_width">104dp</dimen> + <dimen name="global_actions_grid_top_padding">8dp</dimen> + <dimen name="global_actions_grid_bottom_padding">8dp</dimen> + <dimen name="global_actions_grid_left_padding">4dp</dimen> + <dimen name="global_actions_grid_right_padding">4dp</dimen> + + <dimen name="global_actions_grid_item_side_margin">12dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen> + <dimen name="global_actions_top_padding">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> @@ -982,8 +994,8 @@ <dimen name="bubble_view_padding">0dp</dimen> <!-- Padding between bubbles when displayed in expanded state --> <dimen name="bubble_padding">8dp</dimen> - <!-- Size of the collapsed bubble --> - <dimen name="bubble_size">56dp</dimen> + <!-- Size of individual bubbles. --> + <dimen name="individual_bubble_size">56dp</dimen> <!-- How much to inset the icon in the circle --> <dimen name="bubble_icon_inset">16dp</dimen> <!-- Padding around the view displayed when the bubble is expanded --> @@ -1000,10 +1012,20 @@ <dimen name="bubble_expanded_header_height">48dp</dimen> <!-- Left and right padding applied to the header. --> <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen> + <!-- How far, horizontally, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_x_distance">100dp</dimen> + <!-- How far, vertically, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_y_distance">500dp</dimen> <!-- Max width of the message bubble--> <dimen name="bubble_message_max_width">144dp</dimen> <!-- Min width of the message bubble --> <dimen name="bubble_message_min_width">32dp</dimen> <!-- Interior padding of the message bubble --> <dimen name="bubble_message_padding">4dp</dimen> + <!-- Offset between bubbles in their stacked position. --> + <dimen name="bubble_stack_offset">5dp</dimen> + <!-- How far offscreen the bubble stack rests. --> + <dimen name="bubble_stack_offscreen">5dp</dimen> + <!-- How far down the screen the stack starts. --> + <dimen name="bubble_stack_starting_offset_y">100dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index bd34beac7fd6..6a6845742788 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -68,6 +68,7 @@ <item type="id" name="panel_alpha_animator_tag"/> <item type="id" name="panel_alpha_animator_start_tag"/> <item type="id" name="panel_alpha_animator_end_tag"/> + <item type="id" name="cross_fade_layer_type_changed_tag"/> <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> @@ -115,6 +116,14 @@ <item type="id" name="aod_mask_transition_progress_end_tag" /> <item type="id" name="aod_mask_transition_progress_start_tag" /> + <!-- For saving DynamicAnimation physics animations as view tags. --> + <item type="id" name="translation_x_dynamicanimation_tag"/> + <item type="id" name="translation_y_dynamicanimation_tag"/> + <item type="id" name="translation_z_dynamicanimation_tag"/> + <item type="id" name="alpha_dynamicanimation_tag"/> + <item type="id" name="scale_x_dynamicanimation_tag"/> + <item type="id" name="scale_y_dynamicanimation_tag"/> + <!-- Global Actions Menu --> <item type="id" name="global_actions_view" /> </resources> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index fd7a10500f36..e8fabf5a07f1 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -21,4 +21,10 @@ 0) as we can allow the carrier text to stretch as far as needed in the QS footer. --> <integer name="qs_footer_actions_width">-2</integer> <integer name="qs_footer_actions_weight">0</integer> + + <!-- Maximum number of bubbles to render and animate at one time. While the animations used are + lightweight translation animations, this number can be reduced on lower end devices if any + performance issues arise. --> + <integer name="bubbles_max_rendered">5</integer> + </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d64e2f9d21ce..190bd7ae91a2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -159,6 +159,12 @@ <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. --> <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string> + <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_title">USB port disabled</string> + + <!-- Message of USB contaminant presence dialog [CHAR LIMIT=NONE] --> + <string name="usb_contaminant_message">To protect your device from liquid or debris, the USB port is disabled and won\u2019t detect any accessories.\n\nYou\u2019ll be notified when it\u2019s safe to use the USB port again.</string> + <!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running on a phone). [CHAR LIMIT=25] --> <string name="compat_mode_on">Zoom to fill screen</string> @@ -2279,7 +2285,7 @@ app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> <string name="heap_dump_tile_name">Dump SysUI Heap</string> - <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] --> + <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=12] --> <plurals name="ongoing_privacy_chip_multiple_apps"> <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> app</item> <item quantity="few"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</item> diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index b7d51978fab2..adcb7a125e80 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -17,29 +17,14 @@ package com.android.keyguard; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; -import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.method.SingleLineTransformationMethod; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.TextView; -import com.android.internal.telephony.IccCardConstants; -import com.android.internal.telephony.IccCardConstants.State; -import com.android.internal.telephony.TelephonyIntents; -import com.android.settingslib.WirelessUtils; - -import java.util.List; import java.util.Locale; -import java.util.Objects; public class CarrierText extends TextView { private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -47,77 +32,30 @@ public class CarrierText extends TextView { private static CharSequence mSeparator; - private final boolean mIsEmergencyCallCapable; - - private boolean mTelephonyCapable; - private boolean mShowMissingSim; private boolean mShowAirplaneMode; + private boolean mShouldMarquee; - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - - private WifiManager mWifiManager; - - private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()]; - - private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshCarrierInfo() { - if (DEBUG) Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " - + Boolean.toString(mTelephonyCapable)); - updateCarrierText(); - } - - public void onFinishedGoingToSleep(int why) { - setSelected(false); - }; - - public void onStartedWakingUp() { - setSelected(true); - }; + private CarrierTextController mCarrierTextController; - @Override - public void onTelephonyCapable(boolean capable) { - if (DEBUG) Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " - + Boolean.toString(capable)); - mTelephonyCapable = capable; - updateCarrierText(); - } - - public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { - if (slotId < 0) { - Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId - + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); - return; - } + private CarrierTextController.CarrierTextCallback mCarrierTextCallback = + new CarrierTextController.CarrierTextCallback() { + @Override + public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + setText(info.carrierText); + } - if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState)); - if (getStatusForIccState(simState) == StatusMode.SimIoError) { - mSimErrorState[slotId] = true; - updateCarrierText(); - } else if (mSimErrorState[slotId]) { - mSimErrorState[slotId] = false; - updateCarrierText(); - } - } - }; + @Override + public void startedGoingToSleep() { + setSelected(false); + } - /** - * The status of this lock screen. Primarily used for widgets on LockScreen. - */ - private static enum StatusMode { - Normal, // Normal case (sim card present, it's not locked) - NetworkLocked, // SIM card is 'network locked'. - SimMissing, // SIM card is missing. - SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access - SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times - SimLocked, // SIM card is currently locked - SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure - SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. - SimIoError, // SIM card is faulty - SimUnknown // SIM card is unknown - } + @Override + public void finishedWakingUp() { + setSelected(true); + } + }; public CarrierText(Context context) { this(context, null); @@ -125,8 +63,6 @@ public class CarrierText extends TextView { public CarrierText(Context context, AttributeSet attrs) { super(context, attrs); - mIsEmergencyCallCapable = context.getResources().getBoolean( - com.android.internal.R.bool.config_voice_capable); boolean useAllCaps; TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.CarrierText, 0, 0); @@ -138,132 +74,6 @@ public class CarrierText extends TextView { a.recycle(); } setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); - - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - } - - /** - * Checks if there are faulty cards. Adds the text depending on the slot of the card - * @param text: current carrier text based on the sim state - * @param noSims: whether a valid sim card is inserted - * @return text - */ - private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) { - final CharSequence carrier = ""; - CharSequence carrierTextForSimIOError = getCarrierTextForSimState( - IccCardConstants.State.CARD_IO_ERROR, carrier); - for (int index = 0; index < mSimErrorState.length; index++) { - if (mSimErrorState[index]) { - // In the case when no sim cards are detected but a faulty card is inserted - // overwrite the text and only show "Invalid card" - if (noSims) { - return concatenate(carrierTextForSimIOError, - getContext().getText(com.android.internal.R.string.emergency_calls_only)); - } else if (index == 0) { - // prepend "Invalid card" when faulty card is inserted in slot 0 - text = concatenate(carrierTextForSimIOError, text); - } else { - // concatenate "Invalid card" when faulty card is inserted in slot 1 - text = concatenate(text, carrierTextForSimIOError); - } - } - } - return text; - } - - protected void updateCarrierText() { - boolean allSimsMissing = true; - boolean anySimReadyAndInService = false; - CharSequence displayText = null; - - List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); - final int N = subs.size(); - if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N); - for (int i = 0; i < N; i++) { - int subId = subs.get(i).getSubscriptionId(); - State simState = mKeyguardUpdateMonitor.getSimState(subId); - CharSequence carrierName = subs.get(i).getCarrierName(); - CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); - if (DEBUG) { - Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); - } - if (carrierTextForSimState != null) { - allSimsMissing = false; - displayText = concatenate(displayText, carrierTextForSimState); - } - if (simState == IccCardConstants.State.READY) { - ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); - if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) { - // hack for WFC (IWLAN) not turning off immediately once - // Wi-Fi is disassociated or disabled - if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN - || (mWifiManager.isWifiEnabled() - && mWifiManager.getConnectionInfo() != null - && mWifiManager.getConnectionInfo().getBSSID() != null)) { - if (DEBUG) { - Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); - } - anySimReadyAndInService = true; - } - } - } - } - if (allSimsMissing) { - if (N != 0) { - // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. - // This depends on mPlmn containing the text "Emergency calls only" when the radio - // has some connectivity. Otherwise, it should be null or empty and just show - // "No SIM card" - // Grab the first subscripton, because they all should contain the emergency text, - // described above. - displayText = makeCarrierStringOnEmergencyCapable( - getMissingSimMessage(), subs.get(0).getCarrierName()); - } else { - // We don't have a SubscriptionInfo to get the emergency calls only from. - // Grab it from the old sticky broadcast if possible instead. We can use it - // here because no subscriptions are active, so we don't have - // to worry about MSIM clashing. - CharSequence text = - getContext().getText(com.android.internal.R.string.emergency_calls_only); - Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); - if (i != null) { - String spn = ""; - String plmn = ""; - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { - spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); - } - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { - plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); - } - if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); - if (Objects.equals(plmn, spn)) { - text = plmn; - } else { - text = concatenate(plmn, spn); - } - } - displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); - } - } - - displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing); - // APM (airplane mode) != no carrier state. There are carrier services - // (e.g. WFC = Wi-Fi calling) which may operate in APM. - if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { - displayText = getAirplaneModeMessage(); - } - setText(displayText); - } - - private String getMissingSimMessage() { - return mShowMissingSim && mTelephonyCapable - ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; - } - - private String getAirplaneModeMessage() { - return mShowAirplaneMode - ? getContext().getString(R.string.airplane_mode) : ""; } @Override @@ -271,36 +81,27 @@ public class CarrierText extends TextView { super.onFinishInflate(); mSeparator = getResources().getString( com.android.internal.R.string.kg_text_message_separator); - boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); - setSelected(shouldMarquee); // Allow marquee to work. + mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode, + mShowMissingSim); + mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); + setSelected(mShouldMarquee); // Allow marquee to work. } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (ConnectivityManager.from(mContext).isNetworkSupported( - ConnectivityManager.TYPE_MOBILE)) { - mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); - mKeyguardUpdateMonitor.registerCallback(mCallback); - } else { - // Don't listen and clear out the text when the device isn't a phone. - mKeyguardUpdateMonitor = null; - setText(""); - } + mCarrierTextController.setListening(mCarrierTextCallback); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mKeyguardUpdateMonitor != null) { - mKeyguardUpdateMonitor.removeCallback(mCallback); - } + mCarrierTextController.setListening(null); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - // Only show marquee when visible if (visibility == VISIBLE) { setEllipsize(TextUtils.TruncateAt.MARQUEE); @@ -309,167 +110,6 @@ public class CarrierText extends TextView { } } - /** - * Top-level function for creating carrier text. Makes text based on simState, PLMN - * and SPN as well as device capabilities, such as being emergency call capable. - * - * @param simState - * @param text - * @param spn - * @return Carrier text if not in missing state, null otherwise. - */ - private CharSequence getCarrierTextForSimState(IccCardConstants.State simState, - CharSequence text) { - CharSequence carrierText = null; - StatusMode status = getStatusForIccState(simState); - switch (status) { - case Normal: - carrierText = text; - break; - - case SimNotReady: - // Null is reserved for denoting missing, in this case we have nothing to display. - carrierText = ""; // nothing to display yet. - break; - - case NetworkLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - mContext.getText(R.string.keyguard_network_locked_message), text); - break; - - case SimMissing: - carrierText = null; - break; - - case SimPermDisabled: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText( - R.string.keyguard_permanent_disabled_sim_message_short), - text); - break; - - case SimMissingLocked: - carrierText = null; - break; - - case SimLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_locked_message), - text); - break; - - case SimPukLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_puk_locked_message), - text); - break; - case SimIoError: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_error_message_short), - text); - break; - case SimUnknown: - carrierText = null; - break; - } - - return carrierText; - } - - /* - * Add emergencyCallMessage to carrier string only if phone supports emergency calls. - */ - private CharSequence makeCarrierStringOnEmergencyCapable( - CharSequence simMessage, CharSequence emergencyCallMessage) { - if (mIsEmergencyCallCapable) { - return concatenate(simMessage, emergencyCallMessage); - } - return simMessage; - } - - /** - * Determine the current status of the lock screen given the SIM state and other stuff. - */ - private StatusMode getStatusForIccState(IccCardConstants.State simState) { - // Since reading the SIM may take a while, we assume it is present until told otherwise. - if (simState == null) { - return StatusMode.Normal; - } - - final boolean missingAndNotProvisioned = - !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned() - && (simState == IccCardConstants.State.ABSENT || - simState == IccCardConstants.State.PERM_DISABLED); - - // Assume we're NETWORK_LOCKED if not provisioned - simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; - switch (simState) { - case ABSENT: - return StatusMode.SimMissing; - case NETWORK_LOCKED: - return StatusMode.SimMissingLocked; - case NOT_READY: - return StatusMode.SimNotReady; - case PIN_REQUIRED: - return StatusMode.SimLocked; - case PUK_REQUIRED: - return StatusMode.SimPukLocked; - case READY: - return StatusMode.Normal; - case PERM_DISABLED: - return StatusMode.SimPermDisabled; - case UNKNOWN: - return StatusMode.SimUnknown; - case CARD_IO_ERROR: - return StatusMode.SimIoError; - } - return StatusMode.SimUnknown; - } - - private static CharSequence concatenate(CharSequence plmn, CharSequence spn) { - final boolean plmnValid = !TextUtils.isEmpty(plmn); - final boolean spnValid = !TextUtils.isEmpty(spn); - if (plmnValid && spnValid) { - return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString(); - } else if (plmnValid) { - return plmn; - } else if (spnValid) { - return spn; - } else { - return ""; - } - } - - private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState, - String plmn, String spn) { - int carrierHelpTextId = 0; - StatusMode status = getStatusForIccState(simState); - switch (status) { - case NetworkLocked: - carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; - break; - - case SimMissing: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; - break; - - case SimPermDisabled: - carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; - break; - - case SimMissingLocked: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions; - break; - - case Normal: - case SimLocked: - case SimPukLocked: - break; - } - - return mContext.getText(carrierHelpTextId); - } - private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { private final Locale mLocale; private final boolean mAllCaps; diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java new file mode 100644 index 000000000000..2ce69650b65c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.settingslib.WirelessUtils; +import com.android.systemui.Dependency; +import com.android.systemui.keyguard.WakefulnessLifecycle; + +import java.util.List; +import java.util.Objects; + +/** + * Controller that generates text including the carrier names and/or the status of all the SIM + * interfaces in the device. Through a callback, the updates can be retrieved either as a list or + * separated by a given separator {@link CharSequence}. + */ +public class CarrierTextController { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "CarrierTextController"; + + private final boolean mIsEmergencyCallCapable; + private boolean mTelephonyCapable; + private boolean mShowMissingSim; + private boolean mShowAirplaneMode; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private WifiManager mWifiManager; + private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()]; + private CarrierTextCallback mCarrierTextCallback; + private Context mContext; + private CharSequence mSeparator; + private WakefulnessLifecycle mWakefulnessLifecycle; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedWakingUp() { + mCarrierTextCallback.finishedWakingUp(); + } + + @Override + public void onStartedGoingToSleep() { + mCarrierTextCallback.startedGoingToSleep(); + } + }; + + private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + if (DEBUG) { + Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " + + Boolean.toString(mTelephonyCapable)); + } + updateCarrierText(); + } + + @Override + public void onTelephonyCapable(boolean capable) { + if (DEBUG) { + Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " + + Boolean.toString(capable)); + } + mTelephonyCapable = capable; + updateCarrierText(); + } + + public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { + if (slotId < 0) { + Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId + + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); + return; + } + + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); + if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) { + mSimErrorState[slotId] = true; + updateCarrierText(); + } else if (mSimErrorState[slotId]) { + mSimErrorState[slotId] = false; + updateCarrierText(); + } + } + }; + + /** + * The status of this lock screen. Primarily used for widgets on LockScreen. + */ + private enum StatusMode { + Normal, // Normal case (sim card present, it's not locked) + NetworkLocked, // SIM card is 'network locked'. + SimMissing, // SIM card is missing. + SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access + SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times + SimLocked, // SIM card is currently locked + SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure + SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. + SimIoError, // SIM card is faulty + SimUnknown // SIM card is unknown + } + + /** + * Controller that provides updates on text with carriers names or SIM status. + * Used by {@link CarrierText}. + * + * @param separator Separator between different parts of the text + */ + public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, + boolean showMissingSim) { + mContext = context; + mIsEmergencyCallCapable = context.getResources().getBoolean( + com.android.internal.R.bool.config_voice_capable); + + mShowAirplaneMode = showAirplaneMode; + mShowMissingSim = showMissingSim; + + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mSeparator = separator; + mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); + } + + /** + * Checks if there are faulty cards. Adds the text depending on the slot of the card + * + * @param text: current carrier text based on the sim state + * @param noSims: whether a valid sim card is inserted + * @return text + */ + private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) { + final CharSequence carrier = ""; + CharSequence carrierTextForSimIOError = getCarrierTextForSimState( + IccCardConstants.State.CARD_IO_ERROR, carrier); + for (int index = 0; index < mSimErrorState.length; index++) { + if (mSimErrorState[index]) { + // In the case when no sim cards are detected but a faulty card is inserted + // overwrite the text and only show "Invalid card" + if (noSims) { + return concatenate(carrierTextForSimIOError, + getContext().getText( + com.android.internal.R.string.emergency_calls_only), + mSeparator); + } else if (index == 0) { + // prepend "Invalid card" when faulty card is inserted in slot 0 + text = concatenate(carrierTextForSimIOError, text, mSeparator); + } else { + // concatenate "Invalid card" when faulty card is inserted in slot 1 + text = concatenate(text, carrierTextForSimIOError, mSeparator); + } + } + } + return text; + } + + /** + * Sets the listening status of this controller. If the callback is null, it is set to + * not listening + * + * @param callback Callback to provide text updates + */ + public void setListening(CarrierTextCallback callback) { + if (callback != null) { + mCarrierTextCallback = callback; + if (ConnectivityManager.from(mContext).isNetworkSupported( + ConnectivityManager.TYPE_MOBILE)) { + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + mKeyguardUpdateMonitor.registerCallback(mCallback); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + } else { + // Don't listen and clear out the text when the device isn't a phone. + mKeyguardUpdateMonitor = null; + callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null)); + } + } else { + mCarrierTextCallback = null; + if (mKeyguardUpdateMonitor != null) { + mKeyguardUpdateMonitor.removeCallback(mCallback); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + } + } + } + + protected void updateCarrierText() { + boolean allSimsMissing = true; + boolean anySimReadyAndInService = false; + CharSequence displayText = null; + + List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); + final int numSubs = subs.size(); + final int[] subsIds = new int[numSubs]; + if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); + for (int i = 0; i < numSubs; i++) { + int subId = subs.get(i).getSubscriptionId(); + subsIds[i] = subId; + IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId); + CharSequence carrierName = subs.get(i).getCarrierName(); + CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); + if (DEBUG) { + Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); + } + if (carrierTextForSimState != null) { + allSimsMissing = false; + displayText = concatenate(displayText, carrierTextForSimState, mSeparator); + } + if (simState == IccCardConstants.State.READY) { + ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); + if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) { + // hack for WFC (IWLAN) not turning off immediately once + // Wi-Fi is disassociated or disabled + if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + || (mWifiManager.isWifiEnabled() + && mWifiManager.getConnectionInfo() != null + && mWifiManager.getConnectionInfo().getBSSID() != null)) { + if (DEBUG) { + Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); + } + anySimReadyAndInService = true; + } + } + } + } + if (allSimsMissing) { + if (numSubs != 0) { + // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. + // This depends on mPlmn containing the text "Emergency calls only" when the radio + // has some connectivity. Otherwise, it should be null or empty and just show + // "No SIM card" + // Grab the first subscripton, because they all should contain the emergency text, + // described above. + displayText = makeCarrierStringOnEmergencyCapable( + getMissingSimMessage(), subs.get(0).getCarrierName()); + } else { + // We don't have a SubscriptionInfo to get the emergency calls only from. + // Grab it from the old sticky broadcast if possible instead. We can use it + // here because no subscriptions are active, so we don't have + // to worry about MSIM clashing. + CharSequence text = + getContext().getText(com.android.internal.R.string.emergency_calls_only); + Intent i = getContext().registerReceiver(null, + new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + if (i != null) { + String spn = ""; + String plmn = ""; + if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { + spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); + } + if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { + plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + } + if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); + if (Objects.equals(plmn, spn)) { + text = plmn; + } else { + text = concatenate(plmn, spn, mSeparator); + } + } + displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); + } + } + + displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing); + // APM (airplane mode) != no carrier state. There are carrier services + // (e.g. WFC = Wi-Fi calling) which may operate in APM. + if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { + displayText = getAirplaneModeMessage(); + } + + if (mCarrierTextCallback != null) { + mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo( + displayText, + displayText.toString().split(mSeparator.toString()), + anySimReadyAndInService, + subsIds)); + } + + } + + private Context getContext() { + return mContext; + } + + private String getMissingSimMessage() { + return mShowMissingSim && mTelephonyCapable + ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; + } + + private String getAirplaneModeMessage() { + return mShowAirplaneMode + ? getContext().getString(R.string.airplane_mode) : ""; + } + + /** + * Top-level function for creating carrier text. Makes text based on simState, PLMN + * and SPN as well as device capabilities, such as being emergency call capable. + * + * @return Carrier text if not in missing state, null otherwise. + */ + private CharSequence getCarrierTextForSimState(IccCardConstants.State simState, + CharSequence text) { + CharSequence carrierText = null; + CarrierTextController.StatusMode status = getStatusForIccState(simState); + switch (status) { + case Normal: + carrierText = text; + break; + + case SimNotReady: + // Null is reserved for denoting missing, in this case we have nothing to display. + carrierText = ""; // nothing to display yet. + break; + + case NetworkLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + mContext.getText(R.string.keyguard_network_locked_message), text); + break; + + case SimMissing: + carrierText = null; + break; + + case SimPermDisabled: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText( + R.string.keyguard_permanent_disabled_sim_message_short), + text); + break; + + case SimMissingLocked: + carrierText = null; + break; + + case SimLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_locked_message), + text); + break; + + case SimPukLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_puk_locked_message), + text); + break; + case SimIoError: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_error_message_short), + text); + break; + case SimUnknown: + carrierText = null; + break; + } + + return carrierText; + } + + /* + * Add emergencyCallMessage to carrier string only if phone supports emergency calls. + */ + private CharSequence makeCarrierStringOnEmergencyCapable( + CharSequence simMessage, CharSequence emergencyCallMessage) { + if (mIsEmergencyCallCapable) { + return concatenate(simMessage, emergencyCallMessage, mSeparator); + } + return simMessage; + } + + /** + * Determine the current status of the lock screen given the SIM state and other stuff. + */ + private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) { + // Since reading the SIM may take a while, we assume it is present until told otherwise. + if (simState == null) { + return CarrierTextController.StatusMode.Normal; + } + + final boolean missingAndNotProvisioned = + !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned() + && (simState == IccCardConstants.State.ABSENT + || simState == IccCardConstants.State.PERM_DISABLED); + + // Assume we're NETWORK_LOCKED if not provisioned + simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; + switch (simState) { + case ABSENT: + return CarrierTextController.StatusMode.SimMissing; + case NETWORK_LOCKED: + return CarrierTextController.StatusMode.SimMissingLocked; + case NOT_READY: + return CarrierTextController.StatusMode.SimNotReady; + case PIN_REQUIRED: + return CarrierTextController.StatusMode.SimLocked; + case PUK_REQUIRED: + return CarrierTextController.StatusMode.SimPukLocked; + case READY: + return CarrierTextController.StatusMode.Normal; + case PERM_DISABLED: + return CarrierTextController.StatusMode.SimPermDisabled; + case UNKNOWN: + return CarrierTextController.StatusMode.SimUnknown; + case CARD_IO_ERROR: + return CarrierTextController.StatusMode.SimIoError; + } + return CarrierTextController.StatusMode.SimUnknown; + } + + private static CharSequence concatenate(CharSequence plmn, CharSequence spn, + CharSequence separator) { + final boolean plmnValid = !TextUtils.isEmpty(plmn); + final boolean spnValid = !TextUtils.isEmpty(spn); + if (plmnValid && spnValid) { + return new StringBuilder().append(plmn).append(separator).append(spn).toString(); + } else if (plmnValid) { + return plmn; + } else if (spnValid) { + return spn; + } else { + return ""; + } + } + + private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { + if (!TextUtils.isEmpty(string)) { + list.add(string); + } + return list; + } + + private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState, + String plmn, String spn) { + int carrierHelpTextId = 0; + CarrierTextController.StatusMode status = getStatusForIccState(simState); + switch (status) { + case NetworkLocked: + carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; + break; + + case SimMissing: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; + break; + + case SimPermDisabled: + carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; + break; + + case SimMissingLocked: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions; + break; + + case Normal: + case SimLocked: + case SimPukLocked: + break; + } + + return mContext.getText(carrierHelpTextId); + } + + /** + * Data structure for passing information to CarrierTextController subscribers + */ + public static final class CarrierTextCallbackInfo { + public final CharSequence carrierText; + public final CharSequence[] listOfCarriers; + public final boolean anySimReady; + public final int[] subscriptionIds; + + CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, + boolean anySimReady, int[] subscriptionIds) { + this.carrierText = carrierText; + this.listOfCarriers = listOfCarriers; + this.anySimReady = anySimReady; + this.subscriptionIds = subscriptionIds; + } + } + + /** + * Callback to communicate to Views + */ + public interface CarrierTextCallback { + /** + * Provides updated carrier information. + */ + default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; + + /** + * Notifies the View that the device is going to sleep + */ + default void startedGoingToSleep() {}; + + /** + * Notifies the View that the device finished waking up + */ + default void finishedWakingUp() {}; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index d99f234c26c8..fece94e69a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; +import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.AmbientPulseManager; @@ -278,6 +279,7 @@ public class Dependency extends SystemUI { @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; @Inject Lazy<AutoHideController> mAutoHideController; @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener; + @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper; @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler; @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler; @@ -452,6 +454,8 @@ public class Dependency extends SystemUI { mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); mProviders.put(ClockManager.class, mClockManager::get); + mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); + // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index 0c7a9a9fffd2..85265f458370 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -26,11 +26,11 @@ import android.widget.LinearLayout; * Layout class representing the Global Actions menu which appears when the power button is held. */ public abstract class MultiListLayout extends LinearLayout { - boolean mHasOutsideTouch; - boolean mHasSeparatedView; + protected boolean mHasOutsideTouch; + protected boolean mHasSeparatedView; - int mExpectedSeparatedItemCount; - int mExpectedListItemCount; + protected int mExpectedSeparatedItemCount; + protected int mExpectedListItemCount; public MultiListLayout(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 53cdee549536..4d708908975e 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -47,6 +47,10 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; public class AssistManager implements ConfigurationChangedReceiver { private static final String TAG = "AssistManager"; + + // Note that VERBOSE logging may leak PII (e.g. transcription contents). + private static final boolean VERBOSE = false; + private static final String ASSIST_ICON_METADATA_NAME = "com.android.systemui.action_assist_icon"; @@ -103,16 +107,41 @@ public class AssistManager implements ConfigurationChangedReceiver { protected void registerVoiceInteractionSessionListener() { mAssistUtils.registerVoiceInteractionSessionListener( new IVoiceInteractionSessionListener.Stub() { - @Override - public void onVoiceSessionShown() throws RemoteException { - Log.v(TAG, "Voice open"); - } + @Override + public void onVoiceSessionShown() throws RemoteException { + if (VERBOSE) { + Log.v(TAG, "Voice open"); + } + } - @Override - public void onVoiceSessionHidden() throws RemoteException { - Log.v(TAG, "Voice closed"); - } - }); + @Override + public void onVoiceSessionHidden() throws RemoteException { + if (VERBOSE) { + Log.v(TAG, "Voice closed"); + } + } + + @Override + public void onTranscriptionUpdate(String transcription) { + if (VERBOSE) { + Log.v(TAG, "Transcription Updated: \"" + transcription + "\""); + } + } + + @Override + public void onTranscriptionComplete(boolean immediate) { + if (VERBOSE) { + Log.v(TAG, "Transcription complete (immediate=" + immediate + ")"); + } + } + + @Override + public void onVoiceStateChange(int state) { + if (VERBOSE) { + Log.v(TAG, "Voice state is now " + state); + } + } + }); } public void onConfigurationChanged(Configuration newConfiguration) { @@ -291,8 +320,10 @@ public class AssistManager implements ConfigurationChangedReceiver { } } } catch (PackageManager.NameNotFoundException e) { - Log.v(TAG, "Assistant component " - + component.flattenToShortString() + " not found"); + if (VERBOSE) { + Log.v(TAG, "Assistant component " + + component.flattenToShortString() + " not found"); + } } catch (Resources.NotFoundException nfe) { Log.w(TAG, "Failed to swap drawable from " + component.flattenToShortString(), nfe); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 92d3cc1ae34f..36a813b914d5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -57,7 +57,7 @@ public class BadgedImageView extends ImageView { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setScaleType(ScaleType.CENTER_CROP); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size); mDotRenderer = new BadgeRenderer(mIconSize); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index a457deed7ba4..b7bee30dc640 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -18,9 +18,8 @@ package com.android.systemui.bubbles; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; @@ -229,10 +228,6 @@ public class BubbleController { } mStackView.stackDismissed(); - // Reset the position of the stack (TODO - or should we save / respect last user position?) - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - updateVisibility(); mNotificationEntryManager.updateNotifications(); } @@ -249,16 +244,14 @@ public class BubbleController { BubbleView bubble = mBubbles.get(notif.key); mStackView.updateBubble(bubble, notif, updatePosition); } else { - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { - setPosition = true; mStackView = new BubbleStackView(mContext); ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim // between bubble and the shade int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; sbv.addView(mStackView, bubblePosition, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } @@ -273,11 +266,6 @@ public class BubbleController { } mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); - if (setPosition) { - // Need to add the bubble to the stack before we can know the width - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - } } updateVisibility(); } @@ -423,24 +411,6 @@ public class BubbleController { return mStackView; } - // TODO: factor in PIP location / maybe last place user had it - /** - * Gets an appropriate starting point to position the bubble stack. - */ - private static Point getStartPoint(int size, Point displaySize) { - final int x = displaySize.x - size + EDGE_OVERLAP; - final int y = displaySize.y / 4; - return new Point(x, y); - } - - /** - * Gets an appropriate position for the bubble when the stack is expanded. - */ - static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { - // Same place for now.. - return new Point(EDGE_OVERLAP, size); - } - /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java deleted file mode 100644 index c1063fa54ff2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2018 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.bubbles; - -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; - -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.view.View; -import android.view.WindowManager; - -import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView; - -import java.util.Arrays; - -/** - * Math and animators to move bubbles around the screen. - * - * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip - * movements can be unified maybe? - */ -public class BubbleMovementHelper { - - private static final int MAGNET_ANIM_TIME = 150; - public static final int EDGE_OVERLAP = 0; - - private Context mContext; - private Point mDisplaySize; - - public BubbleMovementHelper(Context context) { - mContext = context; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplaySize = new Point(); - wm.getDefaultDisplay().getSize(mDisplaySize); - } - - /** - * @return the distance between the two provided points. - */ - static double distance(Point p1, Point p2) { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - } - - /** - * @return the y value of a line defined by y = mx+b - */ - static float findY(float m, float b, float x) { - return (m * x) + b; - } - - /** - * @return the x value of a line defined by y = mx+b - */ - static float findX(float m, float b, float y) { - return (y - b) / m; - } - - /** - * Determines a point on the edge of the screen based on the velocity and position. - */ - public Point getPointOnEdge(View bv, Point p, float velX, float velY) { - // Find the slope and the y-intercept - velX = velX == 0 ? 1 : velX; - final float m = velY / velX; - final float b = p.y - m * p.x; - - // There are two lines it can intersect, find the two points - Point pointHoriz = new Point(); - Point pointVert = new Point(); - - if (velX > 0) { - // right - pointHoriz.x = mDisplaySize.x; - pointHoriz.y = (int) findY(m, b, mDisplaySize.x); - } else { - // left - pointHoriz.x = EDGE_OVERLAP; - pointHoriz.y = (int) findY(m, b, 0); - } - if (velY > 0) { - // bottom - pointVert.x = (int) findX(m, b, mDisplaySize.y); - pointVert.y = mDisplaySize.y - getNavBarHeight(); - } else { - // top - pointVert.x = (int) findX(m, b, 0); - pointVert.y = EDGE_OVERLAP; - } - - // Use the point that's closest to the start position - final double distanceToVertPoint = distance(p, pointVert); - final double distanceToHorizPoint = distance(p, pointHoriz); - boolean useVert = distanceToVertPoint < distanceToHorizPoint; - // Check if we're being flung along the current edge, use opposite point in this case - // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but - // this works well enough for now - if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) { - // Flinging along left or right edge, favor vert edge - useVert = true; - - } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) { - // Flinging along top or bottom edge - useVert = false; - } - - if (useVert) { - pointVert.x = capX(pointVert.x, bv); - pointVert.y = capY(pointVert.y, bv); - return pointVert; - - } - pointHoriz.x = capX(pointHoriz.x, bv); - pointHoriz.y = capY(pointHoriz.y, bv); - return pointHoriz; - } - - /** - * @return whether the view is on a side edge of the screen (i.e. left or right). - */ - public boolean onSideEdge(View fv, Point p) { - return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x - - EDGE_OVERLAP - || p.x >= EDGE_OVERLAP; - } - - /** - * @return whether the view is on a top or bottom edge of the screen. - */ - public boolean onTopBotEdge(View bv, Point p) { - return p.y >= getStatusBarHeight() + EDGE_OVERLAP - || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y - - EDGE_OVERLAP; - } - - /** - * @return constrained x value based on screen size and how much a view can overlap with a side - * edge. - */ - public int capX(float x, View bv) { - // Floating things can't stick to top or bottom edges, so figure out if it's closer to - // left or right and just use that side + the overlap. - final float centerX = x + bv.getWidth() / 2; - if (centerX > mDisplaySize.x / 2) { - // Right side - return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP; - } else { - // Left side - return EDGE_OVERLAP; - } - } - - /** - * @return constrained y value based on screen size and how much a view can overlap with a top - * or bottom edge. - */ - public int capY(float y, View bv) { - final int height = bv.getHeight(); - if (y < getStatusBarHeight() + EDGE_OVERLAP) { - return getStatusBarHeight() + EDGE_OVERLAP; - } - if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) { - return mDisplaySize.y - height - EDGE_OVERLAP; - } - return (int) y; - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet animateMagnetTo(final BubbleStackView bv) { - Point pos = bv.getPosition(); - - // Find the distance to each edge - final int leftDistance = pos.x; - final int rightDistance = mDisplaySize.x - leftDistance; - final int topDistance = pos.y; - final int botDistance = mDisplaySize.y - topDistance; - - int smallest; - // Find the closest one - int[] distances = { - leftDistance, rightDistance, topDistance, botDistance - }; - Arrays.sort(distances); - smallest = distances[0]; - - // Animate to the closest edge - Point p = new Point(); - if (smallest == leftDistance) { - p.x = capX(EDGE_OVERLAP, bv); - p.y = capY(topDistance, bv); - } - if (smallest == rightDistance) { - p.x = capX(mDisplaySize.x, bv); - p.y = capY(topDistance, bv); - } - if (smallest == topDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(0, bv); - } - if (smallest == botDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(mDisplaySize.y, bv); - } - return getTranslateAnim(bv, p, MAGNET_ANIM_TIME); - } - - /** - * Animation to fling the provided view. - */ - public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) { - Point pos = bv.getPosition(); - Point endPos = getPointOnEdge(bv, pos, velX, velY); - endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv)); - final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2) - + Math.pow(endPos.y - pos.y, 2)); - final float sumVel = Math.abs(velX) + Math.abs(velY); - final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50); - return getTranslateAnim(bv, endPos, duration); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) { - return getTranslateAnim(v, p, duration, 0); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, - int duration, int startDelay) { - return getTranslateAnim(v, p, duration, startDelay, null); - } - - /** - * Animation to translate the provided view. - * - * @param v the view to translate. - * @param p the point to translate to. - * @param duration the duration of the animation. - * @param startDelay the start delay of the animation. - * @param listener the listener to add to the animation. - * - * @return the animation. - */ - public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration, - int startDelay, AnimatorListener listener) { - Point curPos = v.getPosition(); - final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x); - animX.setDuration(duration); - animX.setStartDelay(startDelay); - animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionX((int) value); - } - }); - - final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y); - animY.setDuration(duration); - animY.setStartDelay(startDelay); - animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionY((int) value); - } - }); - if (listener != null) { - animY.addListener(listener); - } - - AnimatorSet set = new AnimatorSet(); - set.playTogether(animX, animY); - set.setInterpolator(FAST_OUT_SLOW_IN); - return set; - } - - - // TODO -- now that this is in system we should be able to get these better, but ultimately - // makes more sense to move to movement bounds style a la PIP - /** - * Returns the status bar height. - */ - public int getStatusBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } - - /** - * Returns the status bar height. - */ - public int getNavBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dcd121bdb239..b584f6781796 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -16,59 +16,89 @@ package com.android.systemui.bubbles; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.app.ActivityView; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.ExpandedAnimationController; +import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; +import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; -import com.android.systemui.statusbar.notification.stack.ViewState; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { - private static final String TAG = "BubbleStackView"; + + /** + * Friction applied to fling animations. Since the stack must land on one of the sides of the + * screen, we want less friction horizontally so that the stack has a better chance of making it + * to the side without needing a spring. + */ + private static final float FLING_FRICTION_X = 1.15f; + private static final float FLING_FRICTION_Y = 1.5f; + + /** + * Damping ratio to use for the stack spring animation used to spring the stack to its final + * position after a fling. + */ + private static final float SPRING_DAMPING_RATIO = 0.85f; + + /** + * Minimum fling velocity required to trigger moving the stack from one side of the screen to + * the other. + */ + private static final float ESCAPE_VELOCITY = 750f; + private Point mDisplaySize; - private FrameLayout mBubbleContainer; + private final SpringAnimation mExpandedViewXAnim; + private final SpringAnimation mExpandedViewYAnim; + + private PhysicsAnimationLayout mBubbleContainer; + private StackAnimationController mStackAnimationController; + private ExpandedAnimationController mExpandedAnimationController; + private BubbleExpandedViewContainer mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; + private int mExpandedAnimateXDistance; + private int mExpandedAnimateYDistance; private boolean mIsExpanded; private int mExpandedBubbleHeight; private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; - private Point mCollapsedPosition; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -110,8 +140,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F setOnTouchListener(mTouchHandler); Resources res = getResources(); - mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedAnimateXDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); + mExpandedAnimateYDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); @@ -120,6 +154,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + + mStackAnimationController = new StackAnimationController(); + mExpandedAnimationController = new ExpandedAnimationController(); + + mBubbleContainer = new PhysicsAnimationLayout(context); + mBubbleContainer.setMaxRenderedChildren( + getResources().getInteger(R.integer.bubbles_max_rendered)); + mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setElevation(elevation); + mBubbleContainer.setPadding(padding, 0, padding, 0); + mBubbleContainer.setClipChildren(false); + addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mExpandedViewContainer = (BubbleExpandedViewContainer) LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, this /* parent */, false /* attachToRoot */); @@ -128,11 +175,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); - mBubbleContainer = new FrameLayout(context); - mBubbleContainer.setElevation(elevation); - mBubbleContainer.setPadding(padding, 0, padding, 0); - mBubbleContainer.setClipChildren(false); - addView(mBubbleContainer); + mExpandedViewXAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); + mExpandedViewXAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + + mExpandedViewYAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); + mExpandedViewYAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); setClipChildren(false); } @@ -144,38 +199,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } @Override - public void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - - int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - if (mIsExpanded) { - ViewGroup parent = (ViewGroup) getParent(); - int parentWidth = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY); - int parentHeight = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec); - - int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight); - setMeasuredDimension(widthSpec, parentHeight); - } else { - // Not expanded - measureChild(mExpandedViewContainer, 0, 0); - - // Bubbles are translated a little to stack on top of each other - widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec); - - heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(), - MeasureSpec.EXACTLY); - setMeasuredDimension(widthSpec, heightSpec); - } - } - - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getRawX(); float y = ev.getRawY(); @@ -293,9 +316,11 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F boolean updatePosition) { bubbleView.update(entry); if (updatePosition && !mIsExpanded) { - // If alerting it gets promoted to top of the stack - mBubbleContainer.removeView(bubbleView); - mBubbleContainer.addView(bubbleView, 0); + // If alerting it gets promoted to top of the stack. + if (mBubbleContainer.indexOfChild(bubbleView) != 0) { + mBubbleContainer.removeViewAndThen(bubbleView, + () -> mBubbleContainer.addView(bubbleView, 0)); + } requestUpdate(); } if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { @@ -359,36 +384,51 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; updateExpandedBubble(); + applyCurrentState(); + //requestUpdate(); + + mIsAnimating = true; + + Runnable updateAfter = () -> { + applyCurrentState(); + mIsAnimating = false; + requestUpdate(); + }; if (shouldExpand) { - // Save current position so that we might return there - savePosition(); + mBubbleContainer.setController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack( + mStackAnimationController.getStackPosition(), updateAfter); + } else { + mBubbleContainer.cancelAllAnimations(); + mExpandedAnimationController.collapseBackToStack( + () -> { + mBubbleContainer.setController(mStackAnimationController); + updateAfter.run(); + }); } - // Determine the translation for the stack - Point position = shouldExpand - ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize) - : mCollapsedPosition; - int delay = shouldExpand ? 0 : 100; - AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position, - 200, delay, null); - if (!shouldExpand) { - // First collapse the stack, then translate, maybe should expand at same time? - animateStackExpansion(() -> translationAnim.start()); - } else { - // First translate, then expand - translationAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mIsAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - animateStackExpansion(() -> mIsAnimating = false); - } - }); - translationAnim.start(); + final float xStart = + mStackAnimationController.getStackPosition().x < getWidth() / 2 + ? -mExpandedAnimateXDistance + : mExpandedAnimateXDistance; + + final float yStart = Math.min( + mStackAnimationController.getStackPosition().y, + mExpandedAnimateYDistance); + final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding; + + if (shouldExpand) { + mExpandedViewContainer.setTranslationX(xStart); + mExpandedViewContainer.setTranslationY(yStart); + mExpandedViewContainer.setAlpha(0f); } + + mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart); + mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart); + mExpandedViewContainer.animate() + .setDuration(100) + .alpha(shouldExpand ? 1f : 0f); } } @@ -401,14 +441,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F + mBubbleContainer.getPaddingStart(); } - /** - * Saves the current position of the stack, used to save user placement of the stack to - * return to after an animation. - */ - private void savePosition() { - mCollapsedPosition = getPosition(); - } - private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { if (mExpandListener != null) { NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; @@ -420,31 +452,154 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F return getBubbleAt(0); } - private BubbleView getBubbleAt(int i) { + /** Return the BubbleView at the given index from the bubble container. */ + public BubbleView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i ? (BubbleView) mBubbleContainer.getChildAt(i) : null; } @Override - public void setPosition(int x, int y) { - setPositionX(x); - setPositionY(y); + public void setPosition(float x, float y) { + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); } @Override - public void setPositionX(int x) { - setTranslationX(x); + public void setPositionX(float x) { + // Unsupported, use setPosition(x, y). } @Override - public void setPositionY(int y) { - setTranslationY(y); + public void setPositionY(float y) { + // Unsupported, use setPosition(x, y). } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return mStackAnimationController.getStackPosition(); + } + + /** Called when a drag operation on an individual bubble has started. */ + public void onBubbleDragStart(BubbleView bubble) { + // TODO: Save position and snap back if not dismissed. + } + + /** Called with the coordinates to which an individual bubble has been dragged. */ + public void onBubbleDragged(BubbleView bubble, float x, float y) { + bubble.setTranslationX(x); + bubble.setTranslationY(y); + } + + /** Called when a drag operation on an individual bubble has finished. */ + public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + } + + void onDragStart() { + if (mIsExpanded) { + return; + } + + mStackAnimationController.cancelStackPositionAnimations(); + mBubbleContainer.setController(mStackAnimationController); + mIsAnimating = false; + } + + void onDragged(float x, float y) { + // TODO: We can drag if animating - just need to reroute inflight anims to drag point. + if (mIsExpanded) { + return; + } + + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); + } + + void onDragFinish(float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + + if (mIsExpanded || mIsAnimating) { + return; + } + + final boolean stackOnLeftSide = x + - mBubbleContainer.getChildAt(0).getWidth() / 2 + < mDisplaySize.x / 2; + + final boolean stackShouldFlingLeft = stackOnLeftSide + ? velX < ESCAPE_VELOCITY + : velX < -ESCAPE_VELOCITY; + + final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion(); + + // Target X translation (either the left or right side of the screen). + final float destinationRelativeX = stackShouldFlingLeft + ? stackBounds.left : stackBounds.right; + + // Minimum velocity required for the stack to make it to the side of the screen. + final float escapeVelocity = getMinXVelocity( + x, + destinationRelativeX, + FLING_FRICTION_X); + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so + // that it'll make it all the way to the side of the screen. + final float startXVelocity = stackShouldFlingLeft + ? Math.min(escapeVelocity, velX) + : Math.max(escapeVelocity, velX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + startXVelocity, + FLING_FRICTION_X, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + destinationRelativeX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + velY, + FLING_FRICTION_Y, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + /* destination */ null); + } + + /** + * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a + * given frictional force. + * + * This is not derived using real math, I just made it up because the math in FlingAnimation + * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it + * to the edge via Fling, it'll get Spring'd there anyway. + * + * TODO(tsuji, or someone who likes math): Figure out math. + */ + private float getMinXVelocity(float x, float destX, float friction) { + return (destX - x) * (friction * 5) + ESCAPE_VELOCITY; + } + + @Override + public void getBoundsOnScreen(Rect outRect) { + if (!mIsExpanded) { + mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); + } else { + mBubbleContainer.getBoundsOnScreen(outRect); + } + } + + private int getStatusBarHeight() { + if (getRootWindowInsets() != null) { + WindowInsets insets = getRootWindowInsets(); + return Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + } + + return 0; } private boolean isIntersecting(View view, float x, float y) { @@ -478,22 +633,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); mExpandedViewContainer.setExpandedView(expandedView); - expandedView.setCallback(new ActivityView.StateCallback() { - @Override - public void onActivityViewReady(ActivityView view) { - Log.d(TAG, "onActivityViewReady(" - + mExpandedBubble.getEntry().key + "): " + view); - view.startActivity(intent); - } - - @Override - public void onActivityViewDestroyed(ActivityView view) { - NotificationEntry entry = mExpandedBubble != null - ? mExpandedBubble.getEntry() : null; - Log.d(TAG, "onActivityViewDestroyed(key=" - + ((entry != null) ? entry.key : "(none)") + "): " + view); - } - }); } else { // Bubble with notification view expanded state ExpandableNotificationRow row = mExpandedBubble.getRowView(); @@ -510,9 +649,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setHeaderText(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); + float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition((int) pointerPosition); } private void applyCurrentState() { @@ -522,7 +660,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { - mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); View expandedView = mExpandedViewContainer.getExpandedView(); if (expandedView instanceof ActivityView) { if (expandedView.isAttachedToWindow()) { @@ -537,53 +674,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(); bv.setZ(bubbsCount - i); - - int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; - ViewState viewState = new ViewState(); - viewState.initFrom(bv); - viewState.xTranslation = transX; - viewState.applyToView(bv); - - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - bv.setTag(new Point(transX, 0)); - } - } - } - - private void animateStackExpansion(Runnable endRunnable) { - int childCount = mBubbleContainer.getChildCount(); - for (int i = 0; i < childCount; i++) { - BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i); - int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i; - int duration = childCount > 1 ? 200 : 0; - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - child.setTag(new Point(transX, 0)); - } - ViewPropertyAnimator anim = child - .animate() - .setStartDelay(15 * i) - .setDuration(duration) - .setInterpolator(mIsExpanded - ? new OvershootInterpolator() - : new AccelerateInterpolator()) - .translationY(0) - .translationX(transX); - final int fi = i; - // Probably want this choreographed with translation somehow / make it snappier - anim.withStartAction(() -> mIsAnimating = true); - anim.withEndAction(() -> { - if (endRunnable != null) { - endRunnable.run(); - } - if (fi == mBubbleContainer.getChildCount() - 1) { - applyCurrentState(); - mIsAnimating = false; - requestUpdate(); - } - }); - anim.start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 97784b0f4f93..22cd2fcc3e72 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -19,7 +19,7 @@ package com.android.systemui.bubbles; import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY; import android.content.Context; -import android.graphics.Point; +import android.graphics.PointF; import android.os.Handler; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -37,18 +37,16 @@ class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; - private BubbleMovementHelper mMovementHelper; // The position of the bubble on down event - private int mBubbleDownPosX; - private int mBubbleDownPosY; + private float mBubbleDownPosX; + private float mBubbleDownPosY; // The touch position on down event - private int mDownX = -1; - private int mDownY = -1; + private float mDownX = -1; + private float mDownY = -1; private boolean mMovedEnough; private int mTouchSlopSquared; - private float mMinFlingVelocity; private VelocityTracker mVelocityTracker; private boolean mInDismissTarget; @@ -71,32 +69,27 @@ class BubbleTouchHandler implements View.OnTouchListener { /** * Sets the position of the view. */ - void setPosition(int x, int y); + void setPosition(float x, float y); /** * Sets the x position of the view. */ - void setPositionX(int x); + void setPositionX(float x); /** * Sets the y position of the view. */ - void setPositionY(int y); + void setPositionY(float y); /** * @return the position of the view. */ - Point getPosition(); + PointF getPosition(); } public BubbleTouchHandler(Context context) { final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mTouchSlopSquared = touchSlop * touchSlop; - - // Multiply by 3 for better fling - mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3; - - mMovementHelper = new BubbleMovementHelper(context); mDismissViewController = new PipDismissViewController(context); } @@ -119,9 +112,11 @@ class BubbleTouchHandler implements View.OnTouchListener { FloatingView floatingView = (FloatingView) targetView; boolean isBubbleStack = floatingView instanceof BubbleStackView; - Point startPos = floatingView.getPosition(); - int rawX = (int) event.getRawX(); - int rawY = (int) event.getRawY(); + PointF startPos = floatingView.getPosition(); + float rawX = event.getRawX(); + float rawY = event.getRawY(); + float x = mBubbleDownPosX + rawX - mDownX; + float y = mBubbleDownPosY + rawY - mDownY; switch (action) { case MotionEvent.ACTION_DOWN: trackMovement(event); @@ -134,6 +129,13 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; mMovedEnough = false; + + if (isBubbleStack) { + stack.onDragStart(); + } else { + stack.onBubbleDragStart((BubbleView) floatingView); + } + break; case MotionEvent.ACTION_MOVE: @@ -145,22 +147,23 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; } - final int deltaX = rawX - mDownX; - final int deltaY = rawY - mDownY; + final float deltaX = rawX - mDownX; + final float deltaY = rawY - mDownY; if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) { mMovedEnough = true; } - int x = mBubbleDownPosX + rawX - mDownX; - int y = mBubbleDownPosY + rawY - mDownY; if (mMovedEnough) { - if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) { + if (floatingView instanceof BubbleView) { mBubbleDraggingOut = ((BubbleView) floatingView); + stack.onBubbleDragged(mBubbleDraggingOut, x, y); + } else { + stack.onDragged(x, y); } - floatingView.setPosition(x, y); } // TODO - when we're in the target stick to it / animate in some way? - mInDismissTarget = mDismissViewController.updateTarget((View) floatingView); + mInDismissTarget = mDismissViewController.updateTarget( + isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView); break; case MotionEvent.ACTION_CANCEL: @@ -181,19 +184,9 @@ class BubbleTouchHandler implements View.OnTouchListener { final float velX = mVelocityTracker.getXVelocity(); final float velY = mVelocityTracker.getYVelocity(); if (isBubbleStack) { - if ((Math.abs(velY) > mMinFlingVelocity) - || (Math.abs(velX) > mMinFlingVelocity)) { - // It's being flung somewhere - mMovementHelper.animateFlingTo(stack, velX, velY).start(); - } else { - // Magnet back to nearest edge - mMovementHelper.animateMagnetTo(stack).start(); - } + stack.onDragFinish(x, y, velX, velY); } else { - // Individual bubble got dragged but not dismissed.. lets animate it back - // into position - Point toGoTo = (Point) ((View) floatingView).getTag(); - mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); + stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY); } } else if (floatingView.equals(stack.getExpandedBubble())) { stack.collapseStack(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 7b6e79be64db..dc948325e27a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -22,7 +22,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -33,6 +33,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.graphics.ColorUtils; @@ -59,6 +60,8 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati private NotificationEntry mEntry; private PendingIntent mAppOverlayIntent; private ActivityView mActivityView; + private boolean mActivityViewReady; + private boolean mActivityViewStarted; public BubbleView(Context context) { this(context, null); @@ -191,10 +194,10 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati fraction = showDot ? fraction : 1 - fraction; mBadgedImageView.setDotScale(fraction); }).withEndAction(() -> { - if (!showDot) { - mBadgedImageView.setShowDot(false); - } - }).start(); + if (!showDot) { + mBadgedImageView.setShowDot(false); + } + }).start(); } } @@ -231,8 +234,21 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati */ public ActivityView getActivityView() { if (mActivityView == null) { - mActivityView = new ActivityView(mContext); + mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, + true /* singleTaskInstance */); Log.d(TAG, "[getActivityView] created: " + mActivityView); + mActivityView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + mActivityViewReady = true; + mActivityView.startActivity(mAppOverlayIntent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + mActivityViewReady = false; + } + }); } return mActivityView; } @@ -244,46 +260,44 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati if (mActivityView == null) { return; } - // HACK: Only release if initialized. There's no way to know if the ActivityView has - // been initialized. Calling release() if it hasn't been initialized will crash. - + if (!mActivityViewReady) { + // release not needed, never initialized? + mActivityView = null; + return; + } + // HACK: release() will crash if the view is not attached. if (!mActivityView.isAttachedToWindow()) { - // HACK: release() will crash if the view is not attached. - mActivityView.setVisibility(View.GONE); - tmpParent.addView(mActivityView, new ViewGroup.LayoutParams( + tmpParent.addView(mActivityView, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } - try { - mActivityView.release(); - } catch (IllegalStateException ex) { - Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); - } + + mActivityView.release(); ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); mActivityView = null; } @Override - public void setPosition(int x, int y) { + public void setPosition(float x, float y) { setPositionX(x); setPositionY(y); } @Override - public void setPositionX(int x) { + public void setPositionX(float x) { setTranslationX(x); } @Override - public void setPositionY(int y) { + public void setPositionY(float y) { setTranslationY(y); } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return new PointF(getTranslationX(), getTranslationY()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java new file mode 100644 index 000000000000..4f870f6ceffc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.graphics.PointF; +import android.view.View; +import android.view.WindowInsets; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their expanded state, or animating to/from the + * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be + * dismissed. + */ +public class ExpandedAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + + /** + * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack} + * and used to return to stack form in {@link #collapseBackToStack}. + */ + private PointF mExpandedFrom; + + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ + private float mStackOffsetPx; + /** Spacing between bubbles in the expanded state. */ + private float mBubblePaddingPx; + /** Size of each bubble. */ + private float mBubbleSizePx; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + mStackOffsetPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_stack_offset); + mBubblePaddingPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_padding); + mBubbleSizePx = layout.getResources().getDimensionPixelSize( + R.dimen.individual_bubble_size); + } + + /** + * Animates expanding the bubbles into a row along the top of the screen. + * + * @return The y-value to which the bubbles were expanded, in case that's useful. + */ + public float expandFromStack(PointF expandedFrom, Runnable after) { + mExpandedFrom = expandedFrom; + + // How much to translate the next bubble, so that it is not overlapping the previous one. + float translateNextBubbleXBy = mBubblePaddingPx; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY()); + translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx; + } + + runAfterTranslationsEnd(after); + return getExpandedY(); + } + + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(Runnable after) { + // Stack to the left if we're going to the left, or right if not. + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex( + i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y); + } + + runAfterTranslationsEnd(after); + } + + /** The Y value of the row of expanded bubbles. */ + private float getExpandedY() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + if (insets != null) { + return mBubblePaddingPx + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + } + + return mBubblePaddingPx; + } + + /** Runs the given Runnable after all translation-related animations have ended. */ + private void runAfterTranslationsEnd(Runnable after) { + DynamicAnimation.OnAnimationEndListener allEndedListener = + (animation, canceled, value, velocity) -> { + if (!mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y)) { + after.run(); + } + }; + + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y); + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return 0; + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + @Override + void onChildAdded(View child, int index) { + // TODO: Animate the new bubble into the row, and push the other bubbles out of the way. + child.setTranslationY(getExpandedY()); + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // TODO: Animate the bubble out, and pull the other bubbles into its position. + actuallyRemove.run(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java new file mode 100644 index 000000000000..4e0abc8009b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +/** + * End listener that removes itself from its animation when called for the first time. Useful since + * anonymous OnAnimationEndListener instances can't pass themselves to + * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass + * implementation. + */ +public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener { + + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + animation.removeEndListener(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java new file mode 100644 index 000000000000..1ced3a4daac6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * Layout that constructs physics-based animations for each of its children, which behave according + * to settings provided by a {@link PhysicsAnimationController} instance. + * + * See physics-animation-layout.md. + */ +public class PhysicsAnimationLayout extends FrameLayout { + private static final String TAG = "Bubbs.PAL"; + + /** + * Controls the construction, configuration, and use of the physics animations supplied by this + * layout. + */ + abstract static class PhysicsAnimationController { + + /** + * Constant to return from {@link #getNextAnimationInChain} if the animation should not be + * chained at all. + */ + protected static final int NONE = -1; + + /** Set of properties for which the layout should construct physics animations. */ + abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties(); + + /** + * Returns the index of the next animation after the given index in the animation chain, or + * {@link #NONE} if it should not be chained, or if the chain should end at the given index. + * + * If a next index is returned, an update listener will be added to the animation at the + * given index that dispatches value updates to the animation at the next index. This + * creates a 'following' effect. + * + * Typical implementations of this method will return either index + 1, or index - 1, to + * create forward or backward chains between adjacent child views, but this is not required. + */ + abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index); + + /** + * Offsets to be added to the value that chained animations of the given property dispatch + * to subsequent child animations. + * + * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles + * stack off to the left or right side slightly. + */ + abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property); + + /** + * Returns the SpringForce to be used for the given child view's property animation. Despite + * these usually being similar or identical across properties and views, {@link SpringForce} + * also contains the SpringAnimation's final position, so we have to construct a new one for + * each animation rather than using a constant. + */ + abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view); + + /** + * Called when a new child is added at the specified index. Controllers can use this + * opportunity to animate in the new view. + */ + abstract void onChildAdded(View child, int index); + + /** + * Called when a child is to be removed from the layout. Controllers can use this + * opportunity to animate out the new view before calling the provided callback to actually + * remove it. + * + * Controllers should be careful to ensure that actuallyRemove is called on all code paths + * or child views will never be removed. + */ + abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove); + + protected PhysicsAnimationLayout mLayout; + + PhysicsAnimationController() { } + + protected void setLayout(PhysicsAnimationLayout layout) { + this.mLayout = layout; + } + + protected PhysicsAnimationLayout getLayout() { + return mLayout; + } + } + + /** + * End listeners that are called when every child's animation of the given property has + * finished. + */ + protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> + mEndListenerForProperty = new HashMap<>(); + + /** + * List of views that were passed to removeView, but are currently being animated out. These + * views will be actually removed by the controller (via super.removeView) once they're done + * animating out. + */ + private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); + + /** The currently active animation controller. */ + private PhysicsAnimationController mController; + + /** + * The maximum number of children to render and animate at a time. See + * {@link #setMaxRenderedChildren}. + */ + private int mMaxRenderedChildren = 5; + + public PhysicsAnimationLayout(Context context) { + super(context); + } + + /** + * The maximum number of children to render and animate at a time. Any child views added beyond + * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, + * the corresponding property will be set with no animation. + */ + public void setMaxRenderedChildren(int max) { + this.mMaxRenderedChildren = max; + } + + /** + * Sets the animation controller and constructs or reconfigures the layout's physics animations + * to meet the controller's specifications. + */ + public void setController(PhysicsAnimationController controller) { + cancelAllAnimations(); + mEndListenerForProperty.clear(); + + this.mController = controller; + mController.setLayout(this); + + // Set up animations for this controller's animated properties. + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationsForProperty(property); + } + } + + /** + * Sets an end listener that will be called when all child animations for a given property have + * stopped running. + */ + public void setEndListenerForProperty( + DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.put(property, listener); + } + + /** + * Removes the end listener that would have been called when all child animations for a given + * property stopped running. + */ + public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.remove(property); + } + + /** + * Returns the index of the view that precedes the given index, ignoring views that were passed + * to removeView, but are currently being animated out before actually being removed. + * + * @return index of the preceding view, or -1 if there are none. + */ + public int getPrecedingNonRemovedViewIndex(int index) { + for (int i = index + 1; i < getChildCount(); i++) { + View precedingView = getChildAt(i); + if (!mViewsToBeActuallyRemoved.contains(precedingView)) { + return i; + } + } + + return -1; + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + setChildrenVisibility(); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setController is called. + if (mController != null) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + @Override + public void removeView(View view) { + removeViewAndThen(view, /* callback */ null); + } + + /** + * Let the controller know that this view should be removed, and then call the callback once the + * controller has finished any removal animations and the view has actually been removed. + */ + public void removeViewAndThen(View view, Runnable callback) { + if (mController != null) { + final int index = indexOfChild(view); + // Remove the view only if it exists in this layout, and we're not already working on + // animating its removal. + if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { + mViewsToBeActuallyRemoved.add(view); + setChildrenVisibility(); + + // Tell the controller to animate this view out, and call the callback when it wants + // to actually remove the view. + mController.onChildToBeRemoved(view, index, () -> { + removeViewImmediateAndThen(view, callback); + mViewsToBeActuallyRemoved.remove(view); + }); + } + } else { + // Without a controller, nobody will animate this view out, so it gets an unceremonious + // departure. + removeViewImmediateAndThen(view, callback); + } + } + + /** Checks whether any animations of the given properties are still running. */ + public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) { + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : properties) { + if (getAnimationAtIndex(property, i).isRunning()) { + return true; + } + } + } + + return false; + } + + /** Cancels all animations that are running on all child views, for all properties. */ + public void cancelAllAnimations() { + if (mController == null) { + return; + } + + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + getAnimationAtIndex(property, i).cancel(); + } + } + } + + /** + * Animates the property of the child at the given index to the given value, then runs the + * callback provided when the animation ends. + */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel, + Runnable after) { + if (index < getChildCount()) { + final SpringAnimation animation = getAnimationAtIndex(property, index); + if (after != null) { + animation.addEndListener(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + after.run(); + } + }); + } + + if (startVel != Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animation.animateToFinalPosition(value); + } + } + + /** Shortcut to animate a value with a callback, but no start velocity. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + Runnable after) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after); + } + + /** Shortcut to animate a value with a start velocity, but no callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel) { + animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null); + } + + /** Shortcut to animate a value without changing the velocity or providing a callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null); + } + + /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */ + protected void animatePositionForChildAtIndex(int index, float x, float y) { + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x); + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y); + } + + /** Whether the first child would be left of center if translated to the given x value. */ + protected boolean isFirstChildXLeftOfCenter(float x) { + if (getChildCount() > 0) { + return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2; + } else { + return false; // If there's no first child, really anything is correct, right? + } + } + + /** ViewProperty's toString is useless, this returns a readable name for debug logging. */ + protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return "TRANSLATION_X"; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return "TRANSLATION_Y"; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return "SCALE_X"; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return "SCALE_Y"; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return "ALPHA"; + } else { + return "Unknown animation property."; + } + } + + + /** Immediately removes the view, without notifying the controller, then runs the callback. */ + private void removeViewImmediateAndThen(View view, Runnable callback) { + super.removeView(view); + + if (callback != null) { + callback.run(); + } + + setChildrenVisibility(); + } + + /** + * Retrieves the animation of the given property from the view at the given index via the view + * tag system. + */ + private SpringAnimation getAnimationAtIndex( + DynamicAnimation.ViewProperty property, int index) { + return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property)); + } + + /** Sets up SpringAnimations of the given property for each child view in the layout. */ + private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) { + for (int i = 0; i < getChildCount(); i++) { + setUpAnimationForChild(property, getChildAt(i), i); + } + } + + /** Constructs a SpringAnimation of the given property for a child view. */ + private void setUpAnimationForChild( + DynamicAnimation.ViewProperty property, View child, int index) { + SpringAnimation newAnim = new SpringAnimation(child, property); + newAnim.addUpdateListener((animation, value, velocity) -> { + final int nextAnimInChain = + mController.getNextAnimationInChain(property, indexOfChild(child)); + if (nextAnimInChain == PhysicsAnimationController.NONE) { + return; + } + + final int animIndex = indexOfChild(child); + final float offset = + mController.getOffsetForChainedPropertyAnimation(property); + + // If this property's animations should be chained, then check to see if there is a + // subsequent animation within the rendering limit, and if so, tell it to animate to + // this animation's new value (plus the offset). + if (nextAnimInChain < Math.min( + getChildCount(), + mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { + getAnimationAtIndex(property, animIndex + 1) + .animateToFinalPosition(value + offset); + } else if (nextAnimInChain < getChildCount()) { + // If the next child view is not rendered, update the property directly without + // animating it, so that the view is still in the correct state if it later + // becomes visible. + for (int i = nextAnimInChain; i < getChildCount(); i++) { + // 'value' here is the value of the last child within the rendering limit, + // not the first child's value - so we want to subtract the last child's + // index when calculating the offset. + property.setValue(getChildAt(i), value + offset * (i - animIndex)); + } + } + }); + + newAnim.setSpring(mController.getSpringForce(property, child)); + newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property)); + child.setTag(getTagIdForProperty(property), newAnim); + } + + /** Hides children beyond the max rendering count. */ + private void setChildrenVisibility() { + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).setVisibility( + // Ignore views that are animating out when calculating whether to hide the + // view. That is, if we're supposed to render 5 views, but 4 are animating out + // and will soon be removed, render up to 9 views temporarily. + i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) + ? View.VISIBLE + : View.GONE); + } + } + + /** Return a stable ID to use as a tag key for the given property's animations. */ + private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return R.id.translation_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return R.id.translation_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return R.id.scale_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return R.id.scale_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return R.id.alpha_dynamicanimation_tag; + } + + return -1; + } + + /** + * End listener that is added to each individual DynamicAnimation, which dispatches to a single + * listener when every other animation of the given property is no longer running. + * + * This is required since chained DynamicAnimations can stop and start again due to changes in + * upstream animations. This means that adding an end listener to just the last animation is not + * sufficient. By firing only when every other animation on the property has stopped running, we + * ensure that no animation will be restarted after the single end listener is called. + */ + protected class AllAnimationsForPropertyFinishedEndListener + implements DynamicAnimation.OnAnimationEndListener { + private DynamicAnimation.ViewProperty mProperty; + + AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) { + this.mProperty = property; + } + + @Override + public void onAnimationEnd( + DynamicAnimation anim, boolean canceled, float value, float velocity) { + if (!arePropertiesAnimating(mProperty)) { + if (mEndListenerForProperty.containsKey(mProperty)) { + mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value, + velocity); + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java new file mode 100644 index 000000000000..0f5137618258 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.HashMap; +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop + * each other with a slight offset to the left or right (depending on which side of the screen they + * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of + * the screen. + */ +public class StackAnimationController extends + PhysicsAnimationLayout.PhysicsAnimationController { + + private static final String TAG = "Bubbs.StackCtrl"; + + /** Scale factor to use initially for new bubbles being animated in. */ + private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; + + /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ + private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; + + /** + * Values to use for the default {@link SpringForce} provided to the physics animation layout. + */ + private static final float DEFAULT_STIFFNESS = 2500f; + private static final float DEFAULT_BOUNCINESS = 0.85f; + + /** + * The canonical position of the stack. This is typically the position of the first bubble, but + * we need to keep track of it separately from the first bubble's translation in case there are + * no bubbles, or the first bubble was just added and being animated to its new position. + */ + private PointF mStackPosition = new PointF(); + + /** + * Animations on the stack position itself, which would have been started in + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to + * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect) + * to a legal position on the side of the screen. + */ + private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations = + new HashMap<>(); + + /** Horizontal offset of bubbles in the stack. */ + private float mStackOffset; + /** Diameter of the bubbles themselves. */ + private int mIndividualBubbleSize; + /** Size of spacing around the bubbles, separating it from the edge of the screen. */ + private int mBubblePadding; + /** How far offscreen the stack rests. */ + private int mBubbleOffscreen; + /** How far down the screen the stack starts, when there is no pre-existing location. */ + private int mStackStartingVerticalOffset; + + private Point mDisplaySize; + private RectF mAllowableStackPositionRegion; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + + mDisplaySize = new Point(); + WindowManager wm = + (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getSize(mDisplaySize); + } + + /** + * Instantly move the first bubble to the given point, and animate the rest of the stack behind + * it with the 'following' effect. + */ + public void moveFirstBubbleWithStackFollowing(float x, float y) { + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x); + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y); + } + + /** + * The position of the stack - typically the position of the first bubble; if no bubbles have + * been added yet, it will be where the first bubble will go when added. + */ + public PointF getStackPosition() { + return mStackPosition; + } + + /** + * Flings the first bubble along the given property's axis, using the provided configuration + * values. When the animation ends - either by hitting the min/max, or by friction sufficiently + * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final + * position. + */ + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, + float vel, + float friction, + SpringForce spring, + Float finalPosition) { + Log.d(TAG, String.format("Flinging %s.", + PhysicsAnimationLayout.getReadablePropertyName(property))); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + final float currentValue = firstBubbleProperty.getValue(this); + final RectF bounds = getAllowableStackPositionRegion(); + final float min = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.left + : bounds.top; + final float max = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.right + : bounds.bottom; + + FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty); + flingAnimation.setFriction(friction) + .setStartVelocity(vel) + + // If the bubble's property value starts beyond the desired min/max, use that value + // instead so that the animation won't immediately end. If, for example, the user + // drags the bubbles into the navigation bar, but then flings them upward, we want + // the fling to occur despite temporarily having a value outside of the min/max. If + // the bubbles are out of bounds and flung even farther out of bounds, the fling + // animation will halt immediately and the SpringAnimation will take over, springing + // it in reverse to the (legal) final position. + .setMinValue(Math.min(currentValue, min)) + .setMaxValue(Math.max(currentValue, max)) + + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (!canceled) { + springFirstBubbleWithStackFollowing(property, spring, endVelocity, + finalPosition != null + ? finalPosition + : Math.max(min, Math.min(max, endValue))); + } + }); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, flingAnimation); + flingAnimation.start(); + } + + /** + * Cancel any stack position animations that were started by calling + * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end + * listeners. + */ + public void cancelStackPositionAnimations() { + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X); + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y); + + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y); + } + + /** + * Returns the region within which the stack is allowed to rest. This goes slightly off the left + * and right sides of the screen, below the status bar/cutout and above the navigation bar. + * While the stack is not allowed to rest outside of these bounds, it can temporarily be + * animated or dragged beyond them. + */ + public RectF getAllowableStackPositionRegion() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + mAllowableStackPositionRegion = new RectF(); + + if (insets != null) { + mAllowableStackPositionRegion.left = + -mBubbleOffscreen + - mBubblePadding + + Math.max( + insets.getSystemWindowInsetLeft(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetLeft() + : 0); + mAllowableStackPositionRegion.right = + mLayout.getWidth() + - mIndividualBubbleSize + + mBubbleOffscreen + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetRight(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetRight() + : 0); + + mAllowableStackPositionRegion.top = + mBubblePadding + + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + mAllowableStackPositionRegion.bottom = + mLayout.getHeight() + - mIndividualBubbleSize + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetBottom(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() + : 0); + } + + return mAllowableStackPositionRegion; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, // For positioning. + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.ALPHA, // For fading in new bubbles. + DynamicAnimation.SCALE_X, // For 'popping in' new bubbles. + DynamicAnimation.SCALE_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + if (property.equals(DynamicAnimation.TRANSLATION_X) + || property.equals(DynamicAnimation.TRANSLATION_Y)) { + return index + 1; // Just chain them linearly. + } else { + return NONE; + } + } + + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + // Offset to the left if we're on the left, or the right otherwise. + return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x) + ? -mStackOffset : mStackOffset; + } else { + return 0f; + } + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setDampingRatio(DEFAULT_BOUNCINESS) + .setStiffness(DEFAULT_STIFFNESS); + } + + @Override + void onChildAdded(View child, int index) { + // If this is the first child added, position the stack in its starting position. + if (mLayout.getChildCount() == 1) { + moveStackToStartPosition(); + } + + if (mLayout.indexOfChild(child) == 0) { + child.setTranslationY(mStackPosition.y); + + // Pop in the new bubble. + child.setScaleX(ANIMATE_IN_STARTING_SCALE); + child.setScaleY(ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f); + + // Fade in the new bubble. + child.setAlpha(0); + mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f); + + // Start the new bubble 4x the normal offset distance in the opposite direction. We'll + // animate in from this position. Since the animations are chained, when the new bubble + // flies in from the side, it will push the other ones out of the way. + float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); + } + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // Animate the child out, actually removing it once its alpha is zero. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.ALPHA, index, 0f, () -> { + actuallyRemove.run(); + }); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); + + final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); + if (hasPrecedingChild) { + final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); + if (precedingViewIndex >= 0) { + final float offsetX = + getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + mLayout.animatePositionForChildAtIndex( + precedingViewIndex, + mStackPosition.x + (index * offsetX), + mStackPosition.y); + } + } + } + + /** Moves the stack, without any animation, to the starting position. */ + private void moveStackToStartPosition() { + mLayout.post(() -> setStackPosition( + getAllowableStackPositionRegion().right, + getAllowableStackPositionRegion().top + mStackStartingVerticalOffset)); + } + + /** + * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent + * bubbles to animate 'following' to the new location. + */ + private void moveFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float value) { + + // Update the canonical stack position. + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + mStackPosition.x = value; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + mStackPosition.y = value; + } + + if (mLayout.getChildCount() > 0) { + property.setValue(mLayout.getChildAt(0), value); + mLayout.animateValueForChildAtIndex( + property, + /* index */ 1, + value + getOffsetForChainedPropertyAnimation(property)); + } + } + + /** Moves the stack to a position instantly, with no animation. */ + private void setStackPosition(float x, float y) { + Log.d(TAG, String.format("Setting position to (%f, %f).", x, y)); + mStackPosition.set(x, y); + + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(y + (i * yOffset)); + } + } + + /** + * Springs the first bubble to the given final position, with the rest of the stack 'following'. + */ + private void springFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, SpringForce spring, + float vel, float finalPosition) { + + Log.d(TAG, String.format("Springing %s to final position %f.", + PhysicsAnimationLayout.getReadablePropertyName(property), + finalPosition)); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + SpringAnimation springAnimation = + new SpringAnimation(this, firstBubbleProperty) + .setSpring(spring) + .setStartVelocity(vel); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, springAnimation); + springAnimation.animateToFinalPosition(finalPosition); + } + + /** + * Cancels any outstanding first bubble property animations that are running. This does not + * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only + * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. + */ + private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) { + if (mStackPositionAnimations.containsKey(property)) { + mStackPositionAnimations.get(property).cancel(); + } + } + + /** + * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's + * translation and animate the rest of the stack with it. A DynamicAnimation can animate this + * property directly to move the first bubble and cause the stack to 'follow' to the new + * location. + * + * This could also be achieved by simply animating the first bubble view and adding an update + * listener to dispatch movement to the rest of the stack. However, this would require + * duplication of logic in that update handler - it's simpler to keep all logic contained in the + * {@link #moveFirstBubbleWithStackFollowing} method. + */ + private class StackPositionProperty + extends FloatPropertyCompat<StackAnimationController> { + private final DynamicAnimation.ViewProperty mProperty; + + private StackPositionProperty(DynamicAnimation.ViewProperty property) { + super(property.toString()); + mProperty = property; + } + + @Override + public float getValue(StackAnimationController controller) { + return mProperty.getValue(mLayout.getChildAt(0)); + } + + @Override + public void setValue(StackAnimationController controller, float value) { + moveFirstBubbleWithStackFollowing(mProperty, value); + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 7b18fad0e105..f5ac0d39d61f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1090,9 +1090,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + protected int getActionLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid_item; + } + return com.android.systemui.R.layout.global_actions_item; + } + public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent, + View v = inflater.inflate(getActionLayoutId(context), parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); @@ -1498,7 +1505,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.setBackgroundDrawable(mGradientDrawable); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - setContentView(com.android.systemui.R.layout.global_actions_wrapped); + + setContentView(getGlobalActionsLayoutId(context)); mGlobalActionsLayout = (MultiListLayout) findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); @@ -1515,6 +1523,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, setTitle(R.string.global_actions); } + private int getGlobalActionsLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid; + } + return com.android.systemui.R.layout.global_actions_wrapped; + } + private void updateList() { mGlobalActionsLayout.removeAllItems(); ArrayList<Action> separatedActions = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java new file mode 100644 index 000000000000..0e49b5f3cd2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.MultiListLayout; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsGridLayout extends MultiListLayout { + + boolean mBackgroundsSet; + + public GlobalActionsGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void setBackgrounds() { + HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); + HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true, + getContext()); + getListView().setBackground(listBackground); + getSeparatedView().setBackground(separatedViewBackground); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + if (getListView() != null && !mBackgroundsSet) { + setBackgrounds(); + mBackgroundsSet = true; + } + } + + @Override + public void setExpectedListItemCount(int count) { + mExpectedListItemCount = count; + getListView().setExpectedCount(count); + } + + @Override + protected ViewGroup getSeparatedView() { + return findViewById(com.android.systemui.R.id.separated_button); + } + + @Override + protected ListGridLayout getListView() { + return findViewById(android.R.id.list); + } + + @Override + public void removeAllItems() { + ViewGroup separatedList = getSeparatedView(); + ListGridLayout list = getListView(); + if (separatedList != null) { + separatedList.removeAllViews(); + } + if (list != null) { + list.removeAllItems(); + } + } + + @Override + public ViewGroup getParentView(boolean separated, int index) { + if (separated) { + return getSeparatedView(); + } else { + return getListView().getParentView(index); + } + } + + /** + * Not used in this implementation of the Global Actions Menu, but necessary for some others. + */ + @Override + public void setDivisionView(View v) { + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java new file mode 100644 index 000000000000..37755155751f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * Layout which uses nested LinearLayouts to create a grid with the following behavior: + * + * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item + * count. + * * Display and hide sub-lists as needed, depending on the expected item count. + * * Favor bias toward having more rows or columns depending on the orientation of the device + * (TODO(123344999): Implement this, currently always favors adding more rows.) + * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows + * or columns depending on the orientation of the device. + * (TODO(123344999): Implement this, currently always columns.) + * + * While we could implement this behavior with a GridLayout, it would take significantly more + * time and effort, and would require more substantial refactoring of the existing code in + * GlobalActionsDialog, since it would require manipulation of the child items themselves. + * + */ + +public class ListGridLayout extends LinearLayout { + private int mExpectedCount; + private int mRows; + private int mColumns; + + public ListGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Remove all items from this grid. + */ + public void removeAllItems() { + for (int i = 0; i < getChildCount(); i++) { + ViewGroup subList = (ViewGroup) getChildAt(i); + if (subList != null) { + subList.removeAllViews(); + } + } + } + + /** + * Get the parent view associated with the item which should be placed at the given position. + */ + public ViewGroup getParentView(int index) { + ViewGroup firstParent = (ViewGroup) getChildAt(0); + if (mRows == 0) { + return firstParent; + } + int column = (int) Math.floor(index / mRows); + ViewGroup parent = (ViewGroup) getChildAt(column); + return parent != null ? parent : firstParent; + } + + /** + * Sets the expected number of items that this grid will be responsible for rendering. + */ + public void setExpectedCount(int count) { + mExpectedCount = count; + mRows = getRowCount(); + mColumns = getColumnCount(); + + for (int i = 0; i < getChildCount(); i++) { + if (i <= mColumns) { + setSublistVisibility(i, true); + } else { + setSublistVisibility(i, false); + } + } + + } + + private void setSublistVisibility(int index, boolean visible) { + View subList = getChildAt(index); + Log.d("ListGrid", "index: " + index + ", visibility: " + visible); + if (subList != null) { + subList.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + private int getRowCount() { + return (int) Math.ceil(Math.sqrt(mExpectedCount)); + } + + private int getColumnCount() { + return (int) Math.round(Math.sqrt(mExpectedCount)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 77e25e324915..26c6d501f2cb 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -30,6 +30,7 @@ import android.widget.TextView import com.android.systemui.Dependency import com.android.systemui.R import com.android.systemui.plugins.ActivityStarter +import java.util.concurrent.TimeUnit class OngoingPrivacyDialog constructor( val context: Context, @@ -60,7 +61,8 @@ class OngoingPrivacyDialog constructor( setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null) setPositiveButton(R.string.ongoing_privacy_dialog_open_settings, object : DialogInterface.OnClickListener { - val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE) + val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra( + Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1)) @Suppress("DEPRECATION") override fun onClick(dialog: DialogInterface?, which: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index b218e80693b1..2339fae2d1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -30,8 +30,12 @@ import com.android.systemui.Dependency import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController import com.android.systemui.R +import java.lang.ref.WeakReference +import javax.inject.Inject +import javax.inject.Singleton -class PrivacyItemController(val context: Context, val callback: Callback) { +@Singleton +class PrivacyItemController @Inject constructor(val context: Context) { companion object { val OPS = intArrayOf(AppOpsManager.OP_CAMERA, @@ -56,9 +60,10 @@ class PrivacyItemController(val context: Context, val callback: Callback) { private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER) private var listening = false val systemApp = PrivacyApplication(context.getString(R.string.device_services), context) + private val callbacks = mutableListOf<WeakReference<Callback>>() private val notifyChanges = Runnable { - callback.privacyChanged(privacyList) + callbacks.forEach { it.get()?.privacyChanged(privacyList) } } private val updateListAndNotifyChanges = Runnable { @@ -88,8 +93,8 @@ class PrivacyItemController(val context: Context, val callback: Callback) { registerReceiver() } - init { - registerReceiver() + private fun unregisterReceiver() { + context.unregisterReceiver(userSwitcherReceiver) } private fun registerReceiver() { @@ -108,17 +113,41 @@ class PrivacyItemController(val context: Context, val callback: Callback) { bgHandler.post(updateListAndNotifyChanges) } - fun setListening(listen: Boolean) { + @VisibleForTesting + internal fun setListening(listen: Boolean) { if (listening == listen) return listening = listen if (listening) { appOpsController.addCallback(OPS, cb) + registerReceiver() update(true) } else { appOpsController.removeCallback(OPS, cb) + unregisterReceiver() } } + private fun addCallback(callback: WeakReference<Callback>) { + callbacks.add(callback) + if (callbacks.isNotEmpty() && !listening) setListening(true) + // Notify this callback if we didn't set to listening + else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList)) + } + + private fun removeCallback(callback: WeakReference<Callback>) { + // Removes also if the callback is null + callbacks.removeIf { it.get()?.equals(callback.get()) ?: true } + if (callbacks.isEmpty()) setListening(false) + } + + fun addCallback(callback: Callback) { + addCallback(WeakReference(callback)) + } + + fun removeCallback(callback: Callback) { + removeCallback(WeakReference(callback)) + } + private fun updatePrivacyList() { privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } .mapNotNull { toPrivacyItem(it) }.distinct() @@ -149,4 +178,13 @@ class PrivacyItemController(val context: Context, val callback: Callback) { } } } + + private class NotifyChangesToCallback( + private val callback: Callback?, + private val list: List<PrivacyItem> + ) : Runnable { + override fun run() { + callback?.privacyChanged(list) + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index e63f88a898cc..c0ed4b97eaff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -30,14 +30,18 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Bundle; import android.os.UserManager; +import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; @@ -45,7 +49,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.keyguard.CarrierText; +import com.android.keyguard.CarrierTextController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.settingslib.drawable.UserIconDrawable; @@ -68,7 +72,11 @@ import javax.inject.Inject; import javax.inject.Named; public class QSFooterImpl extends FrameLayout implements QSFooter, - OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback { + OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback, + CarrierTextController.CarrierTextCallback { + + private static final int SIM_SLOTS = 2; + private static final String TAG = "QSFooterImpl"; private final ActivityStarter mActivityStarter; private final UserInfoController mUserInfoController; @@ -77,7 +85,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private SettingsButton mSettingsButton; protected View mSettingsContainer; private PageIndicator mPageIndicator; - private CarrierText mCarrierText; private boolean mQsDisabled; private QSPanel mQsPanel; @@ -99,12 +106,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private View mActionsContainer; private View mDragHandle; - private View mMobileGroup; - private ImageView mMobileSignal; - private ImageView mMobileRoaming; + + private View mCarrierDivider; + private ViewGroup mMobileFooter; + private View[] mMobileGroups = new View[SIM_SLOTS]; + private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS]; + private TextView[] mCarrierTexts = new TextView[SIM_SLOTS]; + private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS]; + private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS]; + private final CellSignalState[] mInfos = + new CellSignalState[]{new CellSignalState(), new CellSignalState()}; + private final int mColorForeground; - private final CellSignalState mInfo = new CellSignalState(); private OnClickListener mExpandClickListener; + private CarrierTextController mCarrierTextController; @Inject public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -134,10 +149,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mSettingsContainer = findViewById(R.id.settings_button_container); mSettingsButton.setOnClickListener(this); - mMobileGroup = findViewById(R.id.mobile_combo); - mMobileSignal = findViewById(R.id.mobile_signal); - mMobileRoaming = findViewById(R.id.mobile_roaming); - mCarrierText = findViewById(R.id.qs_carrier_text); + mMobileFooter = findViewById(R.id.qs_mobile); + mCarrierGroups[0] = findViewById(R.id.carrier1); + mCarrierGroups[1] = findViewById(R.id.carrier2); + + for (int i = 0; i < SIM_SLOTS; i++) { + mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo); + mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal); + mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming); + mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text); + } + mCarrierDivider = findViewById(R.id.qs_carrier_divider); + CharSequence separator = mContext.getString( + com.android.internal.R.string.kg_text_message_separator); + mCarrierTextController = new CarrierTextController(mContext, separator, false, false); mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); @@ -204,8 +229,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private TouchAnimator createFooterAnimator() { return new TouchAnimator.Builder() .addFloat(mDivider, "alpha", 0, 1) - .addFloat(mCarrierText, "alpha", 0, 0, 1) - .addFloat(mMobileGroup, "alpha", 0, 1) + .addFloat(mMobileFooter, "alpha", 0, 0, 1) + .addFloat(mCarrierDivider, "alpha", 0, 1) .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mDragHandle, "alpha", 1, 0, 0) .addFloat(mPageIndicator, "alpha", 0, 1) @@ -332,10 +357,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mNetworkController.addEmergencyListener(this); mNetworkController.addCallback(this); } + mCarrierTextController.setListening(this); } else { mUserInfoController.removeCallback(this); mNetworkController.removeEmergencyListener(this); mNetworkController.removeCallback(this); + mCarrierTextController.setListening(null); } } @@ -358,7 +385,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (v == mSettingsButton) { if (!mDeviceProvisionedController.isCurrentUserSetup()) { // If user isn't setup just unlock the device and dump them back at SUW. - mActivityStarter.postQSRunnableDismissingKeyguard(() -> { }); + mActivityStarter.postQSRunnableDismissingKeyguard(() -> { + }); return; } MetricsLogger.action(mContext, @@ -415,32 +443,64 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } private void handleUpdateState() { - mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE); - if (mInfo.visible) { - mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE); - mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground)); - SignalDrawable d = new SignalDrawable(mContext); - d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground)); - mMobileSignal.setImageDrawable(d); - mMobileSignal.setImageLevel(mInfo.mobileSignalIconId); - - StringBuilder contentDescription = new StringBuilder(); - if (mInfo.contentDescription != null) { - contentDescription.append(mInfo.contentDescription).append(", "); + for (int i = 0; i < SIM_SLOTS; i++) { + mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE); + if (mInfos[i].visible) { + mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE); + mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground)); + SignalDrawable d = new SignalDrawable(mContext); + d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground)); + mMobileSignals[i].setImageDrawable(d); + mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId); + + StringBuilder contentDescription = new StringBuilder(); + if (mInfos[i].contentDescription != null) { + contentDescription.append(mInfos[i].contentDescription).append(", "); + } + if (mInfos[i].roaming) { + contentDescription + .append(mContext.getString(R.string.data_connection_roaming)) + .append(", "); + } + // TODO: show mobile data off/no internet text for 5 seconds before carrier text + if (TextUtils.equals(mInfos[i].typeContentDescription, + mContext.getString(R.string.data_connection_no_internet)) + || TextUtils.equals(mInfos[i].typeContentDescription, + mContext.getString(R.string.cell_data_off_content_description))) { + contentDescription.append(mInfos[i].typeContentDescription); + } + mMobileSignals[i].setContentDescription(contentDescription); } - if (mInfo.roaming) { - contentDescription - .append(mContext.getString(R.string.data_connection_roaming)) - .append(", "); + } + mCarrierDivider.setVisibility( + mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE); + } + + @Override + public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + if (info.anySimReady) { + boolean[] slotSeen = new boolean[SIM_SLOTS]; + for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) { + int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]); + mInfos[slot].visible = true; + slotSeen[slot] = true; + mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim()); + mCarrierGroups[slot].setVisibility(View.VISIBLE); } - // TODO: show mobile data off/no internet text for 5 seconds before carrier text - if (TextUtils.equals(mInfo.typeContentDescription, - mContext.getString(R.string.data_connection_no_internet)) - || TextUtils.equals(mInfo.typeContentDescription, - mContext.getString(R.string.cell_data_off_content_description))) { - contentDescription.append(mInfo.typeContentDescription); + for (int i = 0; i < SIM_SLOTS; i++) { + if (!slotSeen[i]) { + mInfos[i].visible = false; + mCarrierGroups[i].setVisibility(View.GONE); + } } - mMobileSignal.setContentDescription(contentDescription); + handleUpdateState(); + } else { + mInfos[0].visible = false; + mInfos[1].visible = false; + mCarrierTexts[0].setText(info.carrierText); + mCarrierGroups[0].setVisibility(View.VISIBLE); + mCarrierGroups[1].setVisibility(View.GONE); + handleUpdateState(); } } @@ -450,18 +510,23 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming) { - mInfo.visible = statusIcon.visible; - mInfo.mobileSignalIconId = statusIcon.icon; - mInfo.contentDescription = statusIcon.contentDescription; - mInfo.typeContentDescription = typeContentDescription; - mInfo.roaming = roaming; + int slotIndex = SubscriptionManager.getSlotIndex(subId); + if (slotIndex >= SIM_SLOTS) { + Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex); + } + mInfos[slotIndex].visible = statusIcon.visible; + mInfos[slotIndex].mobileSignalIconId = statusIcon.icon; + mInfos[slotIndex].contentDescription = statusIcon.contentDescription; + mInfos[slotIndex].typeContentDescription = typeContentDescription; + mInfos[slotIndex].roaming = roaming; handleUpdateState(); } @Override public void setNoSims(boolean hasNoSims, boolean simDetected) { if (hasNoSims) { - mInfo.visible = false; + mInfos[0].visible = false; + mInfos[1].visible = false; } handleUpdateState(); } @@ -473,4 +538,38 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, String typeContentDescription; boolean roaming; } + + + /** + * TextView that changes its ellipsize value with its visibility. + */ + public static class QSCarrierText extends TextView { + public QSCarrierText(Context context) { + super(context); + } + + public QSCarrierText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + // Only show marquee when visible + if (visibility == VISIBLE) { + setEllipsize(TextUtils.TruncateAt.MARQUEE); + } else { + setEllipsize(TextUtils.TruncateAt.END); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 75ab5dfd2977..2d64ecd99c88 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -180,14 +180,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, NextAlarmController nextAlarmController, ZenModeController zenModeController, BatteryController batteryController, StatusBarIconController statusBarIconController, - ActivityStarter activityStarter) { + ActivityStarter activityStarter, PrivacyItemController privacyItemController) { super(context, attrs); mAlarmController = nextAlarmController; mZenController = zenModeController; mBatteryController = batteryController; mStatusBarIconController = statusBarIconController; mActivityStarter = activityStarter; - mPrivacyItemController = new PrivacyItemController(context, mPICCallback); + mPrivacyItemController = privacyItemController; mShownCount = getStoredShownCount(); } @@ -512,7 +512,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements return; } mHeaderQsPanel.setListening(listening); - mPrivacyItemController.setListening(listening); mListening = listening; if (listening) { @@ -520,9 +519,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements mAlarmController.addCallback(this); mContext.registerReceiver(mRingerReceiver, new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + mPrivacyItemController.addCallback(mPICCallback); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); + mPrivacyItemController.removeCallback(mPICCallback); mContext.unregisterReceiver(mRingerReceiver); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 1155a414b870..e1becdbb42a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -188,7 +188,7 @@ public class CellularTile extends QSTileImpl<SignalState> { state.secondaryLabel = r.getString(R.string.status_bar_airplane); } else if (mobileDataEnabled) { state.state = Tile.STATE_ACTIVE; - state.secondaryLabel = getMobileDataDescription(cb); + state.secondaryLabel = getMobileDataSubscriptionName(cb); } else { state.state = Tile.STATE_INACTIVE; state.secondaryLabel = r.getString(R.string.cell_data_off); @@ -207,16 +207,16 @@ public class CellularTile extends QSTileImpl<SignalState> { state.contentDescription = state.label + ", " + contentDescriptionSuffix; } - private CharSequence getMobileDataDescription(CallbackInfo cb) { - if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) { + private CharSequence getMobileDataSubscriptionName(CallbackInfo cb) { + if (cb.roaming && !TextUtils.isEmpty(cb.dataSubscriptionName)) { String roaming = mContext.getString(R.string.data_connection_roaming); - String dataDescription = cb.dataContentDescription; + String dataDescription = cb.dataSubscriptionName.toString(); return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription); } if (cb.roaming) { return mContext.getString(R.string.data_connection_roaming); } - return cb.dataContentDescription; + return cb.dataSubscriptionName; } @Override @@ -231,7 +231,7 @@ public class CellularTile extends QSTileImpl<SignalState> { private static final class CallbackInfo { boolean airplaneModeEnabled; - String dataContentDescription; + CharSequence dataSubscriptionName; boolean activityIn; boolean activityOut; boolean noSim; @@ -249,7 +249,7 @@ public class CellularTile extends QSTileImpl<SignalState> { // Not data sim, don't display. return; } - mInfo.dataContentDescription = typeContentDescription; + mInfo.dataSubscriptionName = mController.getMobileDataNetworkName(); mInfo.activityIn = activityIn; mInfo.activityOut = activityOut; mInfo.roaming = roaming; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index b04132d09b7c..43ce0b50e03d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -82,7 +82,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> if ("1".equals(Settings.Global.getString(mContext.getContentResolver(), Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE)) && mController.getAutoModeRaw() == -1) { - mController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM); + mController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); Log.i("NightDisplayTile", "Enrolled in forced night display auto mode"); } @@ -127,7 +127,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> @Nullable private String getSecondaryLabel(boolean isNightLightActivated) { switch(mController.getAutoMode()) { - case ColorDisplayController.AUTO_MODE_TWILIGHT: + case ColorDisplayManager.AUTO_MODE_TWILIGHT: // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset. return isNightLightActivated @@ -136,7 +136,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> : mContext.getString( R.string.quick_settings_night_secondary_label_on_at_sunset); - case ColorDisplayController.AUTO_MODE_CUSTOM: + case ColorDisplayManager.AUTO_MODE_CUSTOM_TIME: // User-specified time, approximated to the nearest hour. final @StringRes int toggleTimeStringRes; final LocalTime toggleTime; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 6c3e504da8bd..04534ba06d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; /** @@ -92,9 +93,15 @@ public class CrossFadeHelper { private static void updateLayerType(View view, float alpha) { if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) { - view.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) { - view.setLayerType(View.LAYER_TYPE_NONE, null); + if (view.getLayerType() != View.LAYER_TYPE_HARDWARE) { + view.setLayerType(View.LAYER_TYPE_HARDWARE, null); + view.setTag(R.id.cross_fade_layer_type_changed_tag, true); + } + } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE + && view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) { + if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) { + view.setLayerType(View.LAYER_TYPE_NONE, null); + } } } @@ -114,7 +121,7 @@ public class CrossFadeHelper { .setStartDelay(delay) .setInterpolator(Interpolators.ALPHA_IN) .withEndAction(null); - if (view.hasOverlappingRendering()) { + if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) { view.animate().withLayer(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt new file mode 100644 index 000000000000..494473242a90 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar + +import android.annotation.ColorInt +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Point +import android.graphics.Rect +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicBlur +import android.util.MathUtils +import com.android.internal.graphics.ColorUtils + +import javax.inject.Inject +import javax.inject.Singleton + +private const val COLOR_ALPHA = (255 * 0.7f).toInt() +private const val BLUR_RADIUS = 25f +private const val DOWNSAMPLE = 6 + +@Singleton +class MediaArtworkProcessor @Inject constructor() { + + private val mTmpSize = Point() + private var mArtworkCache: Bitmap? = null + + fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap { + if (mArtworkCache != null) { + return mArtworkCache!! + } + + context.display.getSize(mTmpSize) + val renderScript = RenderScript.create(context) + val rect = Rect(0, 0,artwork.width, artwork.height) + MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) + val inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), + true /* filter */) + val input = Allocation.createFromBitmap(renderScript, inBitmap, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) + val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height, + Bitmap.Config.ARGB_8888) + val output = Allocation.createFromBitmap(renderScript, outBitmap) + val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) + blur.setRadius(BLUR_RADIUS) + blur.setInput(input) + blur.forEach(output) + output.copyTo(outBitmap) + + input.destroy() + output.destroy() + inBitmap.recycle() + + val canvas = Canvas(outBitmap) + canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA)) + return outBitmap + } + + fun clearCache() { + mArtworkCache?.recycle() + mArtworkCache = null + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index f46ded4d61d8..c25b7cfd804c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -39,6 +39,9 @@ public interface NotificationLockscreenUserManager { boolean isCurrentProfile(int userId); + /** Adds a listener to be notified when the current user changes. */ + void addUserChangedListener(UserChangedListener listener); + void destroy(); SparseArray<UserInfo> getCurrentProfiles(); @@ -58,4 +61,9 @@ public interface NotificationLockscreenUserManager { boolean needsRedaction(NotificationEntry entry); boolean userAllowsPrivateNotificationsInPublic(int currentUserId); + + /** Notified when the current user changes. */ + interface UserChangedListener { + void onUserChanged(int userId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index d5f4d0461ba4..4f9d4282dae8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -49,11 +49,14 @@ import com.android.systemui.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Handles keeping track of the current user, profiles, and various things related to hiding @@ -77,6 +80,7 @@ public class NotificationLockscreenUserManagerImpl implements private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); private final UserManager mUserManager; private final IStatusBarService mBarService; + private final List<UserChangedListener> mListeners = new ArrayList<>(); private boolean mShowLockscreenNotifications; private boolean mAllowLockscreenRemoteInput; @@ -111,6 +115,10 @@ public class NotificationLockscreenUserManagerImpl implements updatePublicMode(); mPresenter.onUserSwitched(mCurrentUserId); getEntryManager().getNotificationData().filterAndSort(); + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(mCurrentUserId); + } } else if (Intent.ACTION_USER_ADDED.equals(action)) { updateCurrentProfilesCache(); } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { @@ -130,8 +138,11 @@ public class NotificationLockscreenUserManagerImpl implements final int count = getEntryManager().getNotificationData().getActiveNotifications().size(); final int rank = getEntryManager().getNotificationData().getRank(notificationKey); + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation( + getEntryManager().getNotificationData().get(notificationKey)); final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, - rank, count, true); + rank, count, true, location); try { mBarService.onNotificationClick(notificationKey, nv); } catch (RemoteException e) { @@ -498,6 +509,10 @@ public class NotificationLockscreenUserManagerImpl implements } } + @Override + public void addUserChangedListener(UserChangedListener listener) { + mListeners.add(listener); + } // public void updatePublicMode() { // //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns @@ -537,6 +552,7 @@ public class NotificationLockscreenUserManagerImpl implements public void destroy() { mContext.unregisterReceiver(mBaseBroadcastReceiver); mContext.unregisterReceiver(mAllUsersReceiver); + mListeners.clear(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 7412702abfea..98a3a547ed2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -102,6 +103,7 @@ public class NotificationMediaManager implements Dumpable { private final Context mContext; private final MediaSessionManager mMediaSessionManager; private final ArrayList<MediaListener> mMediaListeners; + private final MediaArtworkProcessor mMediaArtworkProcessor; protected NotificationPresenter mPresenter; private MediaController mMediaController; @@ -133,6 +135,7 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } + mMediaArtworkProcessor.clearCache(); mMediaMetadata = metadata; dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } @@ -143,8 +146,10 @@ public class NotificationMediaManager implements Dumpable { Context context, Lazy<ShadeController> shadeController, Lazy<StatusBarWindowController> statusBarWindowController, - NotificationEntryManager notificationEntryManager) { + NotificationEntryManager notificationEntryManager, + MediaArtworkProcessor mediaArtworkProcessor) { mContext = context; + mMediaArtworkProcessor = mediaArtworkProcessor; mMediaListeners = new ArrayList<>(); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); @@ -366,6 +371,7 @@ public class NotificationMediaManager implements Dumpable { } private void clearCurrentMediaNotificationSession() { + mMediaArtworkProcessor.clearCache(); mMediaMetadata = null; if (mMediaController != null) { if (DEBUG_MEDIA) { @@ -418,7 +424,19 @@ public class NotificationMediaManager implements Dumpable { // might still be null } if (artworkBitmap != null) { - artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap); + int notificationColor; + synchronized (mEntryManager.getNotificationData()) { + NotificationEntry entry = mEntryManager.getNotificationData() + .get(mMediaNotificationKey); + if (entry == null || entry.getRow() == null) { + notificationColor = Color.TRANSPARENT; + } else { + notificationColor = entry.getRow().calculateBgColor(); + } + } + Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap, + notificationColor); + artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); } } boolean allowWhenShade = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 7d6231f27885..31d16211f521 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -54,6 +54,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.RemoteInputView; @@ -181,7 +182,11 @@ public class NotificationRemoteInputManager implements Dumpable { final int rank = mEntryManager.getNotificationData().getRank(key); final Notification.Action action = statusBarNotification.getNotification().actions[actionIndex]; - final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true); + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation( + mEntryManager.getNotificationData().get(key)); + final NotificationVisibility nv = + NotificationVisibility.obtain(key, rank, count, true, location); try { mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false); } catch (RemoteException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 9ef9c94d7cdc..546b2e9df7b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -538,6 +538,7 @@ public class NotificationShelf extends ActivatableNotificationView implements - getIntrinsicHeight()); } float viewEnd = viewStart + fullHeight; + // TODO: fix this check for anchor scrolling. if (expandingAnimated && mAmbientState.getScrollY() == 0 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) { // We are expanding animated. Because we switch to a linear interpolation in this case, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index 573c1f8ee509..a2abcd2f9c8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -23,6 +23,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import java.util.Set; @@ -74,8 +75,10 @@ public class SmartReplyController { boolean generatedByAssistant) { final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); final int rank = mEntryManager.getNotificationData().getRank(entry.key); - final NotificationVisibility nv = - NotificationVisibility.obtain(entry.key, rank, count, true); + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation(entry); + final NotificationVisibility nv = NotificationVisibility.obtain( + entry.key, rank, count, true, location); try { mBarService.onNotificationActionClick( entry.key, actionIndex, action, nv, generatedByAssistant); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 989e781ab5ba..ef5e936f9532 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.NotificationUpdateHandler; import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -160,8 +161,10 @@ public class NotificationEntryManager implements public void performRemoveNotification(StatusBarNotification n) { final int rank = mNotificationData.getRank(n.getKey()); final int count = mNotificationData.getActiveNotifications().size(); + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation(getNotificationData().get(n.getKey())); final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count, - true); + true, location); removeNotificationInternal( n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 8c29fb50f00a..54ed0d9cd8ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -333,6 +333,7 @@ public class NotificationData { mGroupManager.onEntryUpdated(entry, oldSbn); } entry.populateFromRanking(mTmpRanking); + entry.setIsHighPriority(isHighPriority(entry.notification)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index ee551ee96e7b..2e93c3822737 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -153,6 +153,12 @@ public final class NotificationEntry { */ private boolean mUserDismissedBubble; + /** + * Whether this notification is shown to the user as a high priority notification: visible on + * the lock screen/status bar and in the top section in the shade. + */ + private boolean mHighPriority; + public NotificationEntry(StatusBarNotification n) { this(n, null); } @@ -191,6 +197,14 @@ public final class NotificationEntry { return interruption; } + public boolean isHighPriority() { + return mHighPriority; + } + + public void setIsHighPriority(boolean highPriority) { + this.mHighPriority = highPriority; + } + public void setIsBubble(boolean bubbleable) { mIsBubble = bubbleable; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 76d394d197eb..35b7ef42177d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -32,7 +32,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarStateController; @@ -40,6 +39,8 @@ import com.android.systemui.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -128,7 +129,8 @@ public class NotificationLogger implements StateListener { NotificationEntry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean isVisible = mListContainer.isInVisibleLocation(entry); - NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible); + NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible, + getNotificationLocation(entry)); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { // Build new set of visible notifications. @@ -160,6 +162,37 @@ public class NotificationLogger implements StateListener { } }; + /** + * Returns the location of the notification referenced by the given {@link NotificationEntry}. + */ + public static NotificationVisibility.NotificationLocation getNotificationLocation( + NotificationEntry entry) { + if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) { + return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN; + } + return convertNotificationLocation(entry.getRow().getViewState().location); + } + + private static NotificationVisibility.NotificationLocation convertNotificationLocation( + int location) { + switch (location) { + case ExpandableViewState.LOCATION_FIRST_HUN: + return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP; + case ExpandableViewState.LOCATION_HIDDEN_TOP: + return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP; + case ExpandableViewState.LOCATION_MAIN_AREA: + return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA; + case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING: + return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING; + case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN: + return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN; + case ExpandableViewState.LOCATION_GONE: + return NotificationVisibility.NotificationLocation.LOCATION_GONE; + default: + return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN; + } + } + @Inject public NotificationLogger(NotificationListener notificationListener, UiOffloadThread uiOffloadThread, @@ -363,7 +396,9 @@ public class NotificationLogger implements StateListener { * Called when the notification is expanded / collapsed. */ public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { - mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded); + NotificationVisibility.NotificationLocation location = + getNotificationLocation(mEntryManager.getNotificationData().get(key)); + mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location); } /** @@ -397,10 +432,12 @@ public class NotificationLogger implements StateListener { } @VisibleForTesting - void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { + void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded, + NotificationVisibility.NotificationLocation location) { State state = getState(key); state.mIsUserAction = isUserAction; state.mIsExpanded = isExpanded; + state.mLocation = location; maybeNotifyOnNotificationExpansionChanged(key, state); } @@ -416,6 +453,7 @@ public class NotificationLogger implements StateListener { for (NotificationVisibility nv : newlyVisibleAr) { State state = getState(nv.key); state.mIsVisible = true; + state.mLocation = nv.location; maybeNotifyOnNotificationExpansionChanged(nv.key, state); } for (NotificationVisibility nv : noLongerVisibleAr) { @@ -460,10 +498,8 @@ public class NotificationLogger implements StateListener { final State stateToBeLogged = new State(state); mUiOffloadThread.submit(() -> { try { - mBarService.onNotificationExpansionChanged( - key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded, - // TODO (b/120767764): fill in location - ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */); + mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction, + stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal()); } catch (RemoteException e) { Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e); } @@ -477,6 +513,8 @@ public class NotificationLogger implements StateListener { Boolean mIsExpanded; @Nullable Boolean mIsVisible; + @Nullable + NotificationVisibility.NotificationLocation mLocation; private State() {} @@ -484,10 +522,12 @@ public class NotificationLogger implements StateListener { this.mIsUserAction = state.mIsUserAction; this.mIsExpanded = state.mIsExpanded; this.mIsVisible = state.mIsVisible; + this.mLocation = state.mLocation; } private boolean isFullySet() { - return mIsUserAction != null && mIsExpanded != null && mIsVisible != null; + return mIsUserAction != null && mIsExpanded != null && mIsVisible != null + && mLocation != null; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 296c061459bc..bed2426021a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -3092,6 +3092,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */ + public void setDismissRtl(boolean dismissRtl) { + mMenuRow.setDismissRtl(dismissRtl); + } + private static class NotificationViewState extends ExpandableViewState { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index bd1dfb181afe..cb1384cacec8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -25,6 +25,7 @@ import android.app.NotificationChannel; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.metrics.LogMaker; import android.net.Uri; import android.os.ServiceManager; import android.os.UserHandle; @@ -159,7 +160,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx return bindGuts(row, mGutsMenuItem); } - private boolean bindGuts(final ExpandableNotificationRow row, + @VisibleForTesting + protected boolean bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item) { StatusBarNotification sbn = row.getStatusBarNotification(); @@ -298,7 +300,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getIsNonblockable(), isForBlockingHelper, row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE, - row.getEntry().importance); + row.getEntry().importance, + row.getEntry().isHighPriority()); } @@ -389,7 +392,11 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx return false; } - mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS); + LogMaker logMaker = (row.getStatusBarNotification() == null) + ? new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS) + : row.getStatusBarNotification().getLogMaker(); + mMetricsLogger.write(logMaker.setCategory(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION)); // ensure that it's laid but not visible until actually laid out guts.setVisibility(View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 5253e38fd675..2a9a815e12d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -97,8 +96,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int mNumUniqueChannelsInRow; private NotificationChannel mSingleNotificationChannel; private int mStartingChannelImportance; - private int mStartingChannelOrNotificationImportance; - private int mChosenImportance; + private boolean mWasShownHighPriority; + /** + * The last importance level chosen by the user. Null if the user has not chosen an importance + * level; non-null once the user takes an action which indicates an explicit preference. + */ + @Nullable private Integer mChosenImportance; private boolean mIsSingleDefaultChannel; private boolean mIsNonblockable; private StatusBarNotification mSbn; @@ -195,13 +198,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final OnAppSettingsClickListener onAppSettingsClick, boolean isDeviceProvisioned, boolean isNonblockable, - int importance) + int importance, + boolean wasShownHighPriority) throws RemoteException { bindNotification(pm, iNotificationManager, pkg, notificationChannel, numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick, onAppSettingsClick, isDeviceProvisioned, isNonblockable, false /* isBlockingHelper */, false /* isUserSentimentNegative */, - importance); + importance, wasShownHighPriority); } public void bindNotification( @@ -218,7 +222,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isNonblockable, boolean isForBlockingHelper, boolean isUserSentimentNegative, - int importance) + int importance, + boolean wasShownHighPriority) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -231,10 +236,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mCheckSaveListener = checkSaveListener; mOnSettingsClickListener = onSettingsClick; mSingleNotificationChannel = notificationChannel; - int channelImportance = mSingleNotificationChannel.getImportance(); - mStartingChannelImportance = mChosenImportance = channelImportance; - mStartingChannelOrNotificationImportance = - channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance; + mStartingChannelImportance = mSingleNotificationChannel.getImportance(); + mWasShownHighPriority = wasShownHighPriority; mNegativeUserSentiment = isUserSentimentNegative; mIsNonblockable = isNonblockable; mIsForeground = @@ -400,19 +403,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * @return new LogMaker */ private LogMaker importanceChangeLogMaker() { + Integer chosenImportance = + mChosenImportance != null ? mChosenImportance : mStartingChannelImportance; return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE) .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(mChosenImportance - mStartingChannelImportance); + .setSubtype(chosenImportance - mStartingChannelImportance); } private boolean hasImportanceChanged() { return mSingleNotificationChannel != null - && mStartingChannelImportance != mChosenImportance; + && mChosenImportance != null + && (mStartingChannelImportance != mChosenImportance + || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT) + || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT)); } private void saveImportance() { if (!mIsNonblockable || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) { + if (mChosenImportance == null) { + mChosenImportance = mStartingChannelImportance; + } updateImportance(); } } @@ -421,12 +432,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * Commits the updated importance values on the background thread. */ private void updateImportance() { - mMetricsLogger.write(importanceChangeLogMaker()); - - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, - mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, - mStartingChannelImportance, mChosenImportance)); + if (mChosenImportance != null) { + mMetricsLogger.write(importanceChangeLogMaker()); + + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, + mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, + mStartingChannelImportance, mChosenImportance)); + } } private void bindButtons() { @@ -444,11 +458,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G TextView silent = findViewById(R.id.int_silent); TextView alert = findViewById(R.id.int_alert); - boolean isCurrentlyAlerting = - mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT; - block.setOnClickListener(mOnStopOrMinimizeNotifications); - if (isCurrentlyAlerting) { + if (mWasShownHighPriority) { silent.setOnClickListener(mOnToggleSilent); silent.setText(R.string.inline_silent_button_silent); alert.setOnClickListener(mOnKeepShowing); @@ -517,7 +528,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G break; case ACTION_TOGGLE_SILENT: mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT; - if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) { + if (mWasShownHighPriority) { mChosenImportance = IMPORTANCE_LOW; confirmationText.setText(R.string.notification_channel_silenced); } else { @@ -584,9 +595,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public void onFinishedClosing() { - mStartingChannelImportance = mChosenImportance; - if (mChosenImportance != IMPORTANCE_UNSPECIFIED) { - mStartingChannelOrNotificationImportance = mChosenImportance; + if (mChosenImportance != null) { + mStartingChannelImportance = mChosenImportance; } mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index d97162c2ef1e..d83a158b319f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Notification; -import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -78,6 +77,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private ArrayList<MenuItem> mRightMenuItems; private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; + private boolean mDismissRtl; + private boolean mIsForeground; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -238,6 +239,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } private void createMenuViews(boolean resetState, final boolean isForeground) { + mIsForeground = isForeground; + final Resources res = mContext.getResources(); mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); @@ -250,12 +253,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } mAppOpsItem = createAppOpsItem(mContext); if (NotificationUtils.useNewInterruptionModel(mContext)) { - int channelImportance = mParent.getEntry().channel.getImportance(); - int effectiveImportance = - channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED - ? mParent.getEntry().importance : channelImportance; - mInfoItem = createInfoItem(mContext, - effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT); + mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority()); } else { mInfoItem = createInfoItem(mContext); } @@ -268,10 +266,11 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mRightMenuItems.add(mAppOpsItem); mLeftMenuItems.addAll(mRightMenuItems); } else { - mRightMenuItems.add(mInfoItem); - mRightMenuItems.add(mAppOpsItem); + ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems; + menuItems.add(mInfoItem); + menuItems.add(mAppOpsItem); if (!isForeground) { - mRightMenuItems.add(mSnoozeItem); + menuItems.add(mSnoozeItem); } } @@ -729,6 +728,14 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl return getParent().canViewBeDismissed(); } + @Override + public void setDismissRtl(boolean dismissRtl) { + mDismissRtl = dismissRtl; + if (mMenuContainer != null) { + createMenuViews(true, mIsForeground); + } + } + public static class NotificationMenuItem implements MenuItem { View mMenuView; GutsContent mGutsContent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index db7b4fc189aa..4bdc1705c5ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -17,8 +17,15 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.os.Build; import android.view.View; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -42,6 +49,47 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { } @Override + public void onReinflated() { + super.onReinflated(); + + Configuration configuration = mView.getResources().getConfiguration(); + boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + + float[] hsl = new float[] {0f, 0f, 0f}; + ColorUtils.colorToHSL(mBackgroundColor, hsl); + boolean backgroundIsDark = Color.alpha(mBackgroundColor) == 0 + || hsl[1] == 0 && hsl[2] < 0.5; + boolean backgroundHasColor = hsl[1] > 0; + + // Let's invert the notification colors when we're in night mode and + // the notification background isn't colorized. + if (!backgroundIsDark && !backgroundHasColor && nightMode + && mRow.getEntry().targetSdk < Build.VERSION_CODES.Q) { + Paint paint = new Paint(); + ColorMatrix matrix = new ColorMatrix(); + ColorMatrix tmp = new ColorMatrix(); + // Inversion should happen on Y'UV space to conseve the colors and + // only affect the luminosity. + matrix.setRGB2YUV(); + tmp.set(new float[]{ + -1f, 0f, 0f, 0f, 255f, + 0f, 1f, 0f, 0f, 0f, + 0f, 0f, 1f, 0f, 0f, + 0f, 0f, 0f, 1f, 0f + }); + matrix.postConcat(tmp); + tmp.setYUV2RGB(); + matrix.postConcat(tmp); + paint.setColorFilter(new ColorMatrixColorFilter(matrix)); + mView.setLayerType(View.LAYER_TYPE_HARDWARE, paint); + + hsl[2] = 1f - hsl[2]; + mBackgroundColor = ColorUtils.HSLToColor(hsl); + } + } + + @Override protected boolean shouldClearBackgroundOnReapply() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 1efdc56874f3..9258c9971451 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -37,7 +37,7 @@ public abstract class NotificationViewWrapper implements TransformableView { protected final View mView; protected final ExpandableNotificationRow mRow; - private int mBackgroundColor = 0; + protected int mBackgroundColor = 0; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 1b5013dedb0e..c246af57504c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -42,6 +42,8 @@ public class AmbientState { private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>(); private int mScrollY; + private int mAnchorViewIndex; + private int mAnchorViewY; private boolean mDimmed; private ActivatableNotificationView mActivatedChild; private float mOverScrollTopAmount; @@ -130,6 +132,27 @@ public class AmbientState { this.mScrollY = scrollY; } + /** + * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}. + * Other views are laid out outwards from this view in both directions. + */ + public int getAnchorViewIndex() { + return mAnchorViewIndex; + } + + public void setAnchorViewIndex(int anchorViewIndex) { + mAnchorViewIndex = anchorViewIndex; + } + + /** Current Y position of the view at {@link #getAnchorViewIndex()}. */ + public int getAnchorViewY() { + return mAnchorViewY; + } + + public void setAnchorViewY(int anchorViewY) { + mAnchorViewY = anchorViewY; + } + /** Call when dragging begins. */ public void onBeginDrag(ExpandableView view) { mDraggedViews.add(view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 0c37666a79d5..63b34d185c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; @@ -173,13 +174,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final boolean mShouldDrawNotificationBackground; private boolean mLowPriorityBeforeSpeedBump; private final boolean mAllowLongPress; + private boolean mDismissRtl; private float mExpandedHeight; private int mOwnScrollY; + private View mScrollAnchorView; + private int mScrollAnchorViewY; private int mMaxLayoutHeight; private VelocityTracker mVelocityTracker; private OverScroller mScroller; + /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */ + private int mLastScrollerY; + /** + * True if the max position was set to a known position on the last call to {@link #mScroller}. + */ + private boolean mIsScrollerBoundSet; private Runnable mFinishScrollingCallback; private int mTouchSlop; private int mMinimumVelocity; @@ -417,7 +427,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private int mStatusBarState; private int mCachedBackgroundColor; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; - private Runnable mAnimateScroll = this::animateScroll; + private Runnable mReflingAndAnimateScroll = () -> { + if (ANCHOR_SCROLLING) { + maybeReflingScroller(); + } + animateScroll(); + }; private int mCornerRadius; private int mSidePaddings; private final Rect mBackgroundAnimationRect = new Rect(); @@ -511,6 +526,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mDebugPaint.setColor(0xffff0000); mDebugPaint.setStrokeWidth(2); mDebugPaint.setStyle(Paint.Style.STROKE); + mDebugPaint.setTextSize(25f); } mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); @@ -518,8 +534,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd tunerService.addTunable((key, newValue) -> { if (key.equals(LOW_PRIORITY)) { mLowPriorityBeforeSpeedBump = "1".equals(newValue); + } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) { + updateDismissRtlSetting("1".equals(newValue)); } - }, LOW_PRIORITY); + }, LOW_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL); mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override @@ -533,6 +551,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd }); } + private void updateDismissRtlSetting(boolean dismissRtl) { + mDismissRtl = dismissRtl; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); + } + } + } + @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onFinishInflate() { @@ -674,6 +702,30 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + if (DEBUG && ANCHOR_SCROLLING) { + if (mScrollAnchorView instanceof ExpandableNotificationRow) { + canvas.drawRect(0, + mScrollAnchorView.getTranslationY(), + getWidth(), + mScrollAnchorView.getTranslationY() + + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(), + mDebugPaint); + canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200, + mScrollAnchorView.getTranslationY() + 30, mDebugPaint); + int y = (int) mShelf.getTranslationY(); + canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + } + canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100, + getIntrinsicPadding() + 30, mDebugPaint); + canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100, + getHeight() - 30, mDebugPaint); + } + } + @ShadeViewRefactor(RefactorComponent.DECORATOR) private void drawBackground(Canvas canvas) { int lockScreenLeft = mSidePaddings; @@ -970,7 +1022,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() ? 0 : mScroller.getCurrVelocity()); - mAmbientState.setScrollY(mOwnScrollY); + if (ANCHOR_SCROLLING) { + mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView)); + mAmbientState.setAnchorViewY(mScrollAnchorViewY); + } else { + mAmbientState.setScrollY(mOwnScrollY); + } mStackScrollAlgorithm.resetViewStates(mAmbientState); if (!isCurrentlyAnimating() && !mNeedsAnimation) { applyCurrentState(); @@ -1004,7 +1061,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd float end = start + child.getActualHeight(); boolean clip = clipStart > start && clipStart < end || clipEnd >= start && clipEnd <= end; - clip &= !(first && mOwnScrollY == 0); + clip &= !(first && isScrolledToTop()); child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) : ExpandableView.NO_ROUNDNESS); first = false; @@ -1016,19 +1073,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (mChildrenToAddAnimated.isEmpty()) { return; } - for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); - if (mChildrenToAddAnimated.contains(child)) { - int startingPosition = getPositionInLinearLayout(child); - float increasedPaddingAmount = child.getIncreasedPaddingAmount(); - int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements - : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements; - int childHeight = getIntrinsicHeight(child) + padding; - if (startingPosition < mOwnScrollY) { - // This child starts off screen, so let's keep it offscreen to keep the - // others visible - - setOwnScrollY(mOwnScrollY + childHeight); + if (!ANCHOR_SCROLLING) { + for (int i = 0; i < getChildCount(); i++) { + ExpandableView child = (ExpandableView) getChildAt(i); + if (mChildrenToAddAnimated.contains(child)) { + int startingPosition = getPositionInLinearLayout(child); + float increasedPaddingAmount = child.getIncreasedPaddingAmount(); + int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements + : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements; + int childHeight = getIntrinsicHeight(child) + padding; + if (startingPosition < mOwnScrollY) { + // This child starts off screen, so let's keep it offscreen to keep the + // others visible + + setOwnScrollY(mOwnScrollY + childHeight); + } } } } @@ -1047,12 +1106,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); - targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); + if (ANCHOR_SCROLLING) { + // TODO + } else { + targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); - // Only apply the scroll if we're scrolling the view upwards, or the view is so far up - // that it is not visible anymore. - if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { - setOwnScrollY(targetScroll); + // Only apply the scroll if we're scrolling the view upwards, or the view is so + // far up that it is not visible anymore. + if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { + setOwnScrollY(targetScroll); + } } } } @@ -1073,9 +1136,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { - int scrollRange = getScrollRange(); - if (scrollRange < mOwnScrollY) { - setOwnScrollY(scrollRange); + if (ANCHOR_SCROLLING) { + // TODO + } else { + int scrollRange = getScrollRange(); + if (scrollRange < mOwnScrollY) { + setOwnScrollY(scrollRange); + } } } @@ -1453,17 +1520,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean scrollTo(View v) { ExpandableView expandableView = (ExpandableView) v; - int positionInLinearLayout = getPositionInLinearLayout(v); - int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); - int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); - - // Only apply the scroll if we're scrolling the view upwards, or the view is so far up - // that it is not visible anymore. - if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { - mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); - mDontReportNextOverScroll = true; - animateScroll(); - return true; + if (ANCHOR_SCROLLING) { + // TODO + } else { + int positionInLinearLayout = getPositionInLinearLayout(v); + int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); + int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); + + // Only apply the scroll if we're scrolling the view upwards, or the view is so far up + // that it is not visible anymore. + if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { + mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); + mDontReportNextOverScroll = true; + animateScroll(); + return true; + } } return false; } @@ -1484,16 +1555,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public WindowInsets onApplyWindowInsets(WindowInsets insets) { mBottomInset = insets.getSystemWindowInsetBottom(); - int range = getScrollRange(); - if (mOwnScrollY > range) { - // HACK: We're repeatedly getting staggered insets here while the IME is - // animating away. To work around that we'll wait until things have settled. - removeCallbacks(mReclamp); - postDelayed(mReclamp, 50); - } else if (mForcedScroll != null) { - // The scroll was requested before we got the actual inset - in case we need - // to scroll up some more do so now. - scrollTo(mForcedScroll); + if (ANCHOR_SCROLLING) { + // TODO + } else { + int range = getScrollRange(); + if (mOwnScrollY > range) { + // HACK: We're repeatedly getting staggered insets here while the IME is + // animating away. To work around that we'll wait until things have settled. + removeCallbacks(mReclamp); + postDelayed(mReclamp, 50); + } else if (mForcedScroll != null) { + // The scroll was requested before we got the actual inset - in case we need + // to scroll up some more do so now. + scrollTo(mForcedScroll); + } } return insets; } @@ -1502,8 +1577,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private Runnable mReclamp = new Runnable() { @Override public void run() { - int range = getScrollRange(); - mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); + if (ANCHOR_SCROLLING) { + // TODO + } else { + int range = getScrollRange(); + mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); + } mDontReportNextOverScroll = true; mDontClampNextScroll = true; animateScroll(); @@ -1581,20 +1660,39 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } // Top overScroll might not grab all scrolling motion, // we have to scroll as well. - float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; - float newScrollY = mOwnScrollY + scrollAmount; - if (newScrollY > range) { - if (!mExpandedInThisMotion) { - float currentBottomPixels = getCurrentOverScrolledPixels(false); - // We overScroll on the top - setOverScrolledPixels(currentBottomPixels + newScrollY - range, - false /* onTop */, - false /* animate */); + if (ANCHOR_SCROLLING) { + float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; + // TODO: once we're recycling this will need to check the adapter position of the child + ExpandableView lastRow = getLastRowNotGone(); + if (lastRow != null && !lastRow.isInShelf()) { + float distanceToMax = Math.max(0, getMaxPositiveScrollAmount()); + if (scrollAmount > distanceToMax) { + float currentBottomPixels = getCurrentOverScrolledPixels(false); + // We overScroll on the bottom + setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax), + false /* onTop */, + false /* animate */); + mScrollAnchorViewY -= distanceToMax; + scrollAmount = 0f; + } } - setOwnScrollY(range); - scrollAmount = 0.0f; + return scrollAmount; + } else { + float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; + float newScrollY = mOwnScrollY + scrollAmount; + if (newScrollY > range) { + if (!mExpandedInThisMotion) { + float currentBottomPixels = getCurrentOverScrolledPixels(false); + // We overScroll on the bottom + setOverScrolledPixels(currentBottomPixels + newScrollY - range, + false /* onTop */, + false /* animate */); + } + setOwnScrollY(range); + scrollAmount = 0.0f; + } + return scrollAmount; } - return scrollAmount; } /** @@ -1615,18 +1713,37 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } // Bottom overScroll might not grab all scrolling motion, // we have to scroll as well. - float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; - float newScrollY = mOwnScrollY + scrollAmount; - if (newScrollY < 0) { - float currentTopPixels = getCurrentOverScrolledPixels(true); - // We overScroll on the top - setOverScrolledPixels(currentTopPixels - newScrollY, - true /* onTop */, - false /* animate */); - setOwnScrollY(0); - scrollAmount = 0.0f; + if (ANCHOR_SCROLLING) { + float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; + // TODO: once we're recycling this will need to check the adapter position of the child + ExpandableView firstChild = getFirstChildNotGone(); + float top = firstChild.getTranslationY(); + float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY; + if (distanceToTop < -scrollAmount) { + float currentTopPixels = getCurrentOverScrolledPixels(true); + // We overScroll on the top + setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop), + true /* onTop */, + false /* animate */); + mScrollAnchorView = firstChild; + mScrollAnchorViewY = 0; + scrollAmount = 0f; + } + return scrollAmount; + } else { + float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; + float newScrollY = mOwnScrollY + scrollAmount; + if (newScrollY < 0) { + float currentTopPixels = getCurrentOverScrolledPixels(true); + // We overScroll on the top + setOverScrolledPixels(currentTopPixels - newScrollY, + true /* onTop */, + false /* animate */); + setOwnScrollY(0); + scrollAmount = 0.0f; + } + return scrollAmount; } - return scrollAmount; } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -1661,26 +1778,43 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void animateScroll() { if (mScroller.computeScrollOffset()) { - int oldY = mOwnScrollY; - int y = mScroller.getCurrY(); - - if (oldY != y) { - int range = getScrollRange(); - if (y < 0 && oldY >= 0 || y > range && oldY <= range) { - float currVelocity = mScroller.getCurrVelocity(); - if (currVelocity >= mMinimumVelocity) { - mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; + if (ANCHOR_SCROLLING) { + int oldY = mLastScrollerY; + int y = mScroller.getCurrY(); + int deltaY = y - oldY; + if (deltaY != 0) { + int maxNegativeScrollAmount = getMaxNegativeScrollAmount(); + int maxPositiveScrollAmount = getMaxPositiveScrollAmount(); + if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount) + || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) { + // This frame takes us into overscroll, so set the max overscroll based on + // the current velocity + setMaxOverScrollFromCurrentVelocity(); } + customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll); + mLastScrollerY = y; } + } else { + int oldY = mOwnScrollY; + int y = mScroller.getCurrY(); + + if (oldY != y) { + int range = getScrollRange(); + if (y < 0 && oldY >= 0 || y > range && oldY <= range) { + // This frame takes us into overscroll, so set the max overscroll based on + // the current velocity + setMaxOverScrollFromCurrentVelocity(); + } - if (mDontClampNextScroll) { - range = Math.max(range, oldY); + if (mDontClampNextScroll) { + range = Math.max(range, oldY); + } + customOverScrollBy(y - oldY, oldY, range, + (int) (mMaxOverScroll)); } - customOverScrollBy(y - oldY, oldY, range, - (int) (mMaxOverScroll)); } - postOnAnimation(mAnimateScroll); + postOnAnimation(mReflingAndAnimateScroll); } else { mDontClampNextScroll = false; if (mFinishScrollingCallback != null) { @@ -1689,26 +1823,67 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } + private void setMaxOverScrollFromCurrentVelocity() { + float currVelocity = mScroller.getCurrVelocity(); + if (currVelocity >= mMinimumVelocity) { + mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; + } + } + + /** + * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta + * would cause us to exceed the provided maximum overscroll, springs back instead. + * + * This method performs the determination of whether we're exceeding the overscroll and clamps + * the scroll amount if so. The actual scrolling/overscrolling happens in + * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or + * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling). + * + * @param deltaY The (signed) number of pixels to scroll. + * @param scrollY The current scroll position (absolute scrolling only). + * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only). + * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by. + */ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, - int maxOverScrollY) { + private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) { + if (ANCHOR_SCROLLING) { + boolean clampedY = false; + if (deltaY < 0) { + int maxScrollAmount = getMaxNegativeScrollAmount(); + if (maxScrollAmount > Integer.MIN_VALUE) { + maxScrollAmount -= maxOverScrollY; + if (deltaY < maxScrollAmount) { + deltaY = maxScrollAmount; + clampedY = true; + } + } + } else { + int maxScrollAmount = getMaxPositiveScrollAmount(); + if (maxScrollAmount < Integer.MAX_VALUE) { + maxScrollAmount += maxOverScrollY; + if (deltaY > maxScrollAmount) { + deltaY = maxScrollAmount; + clampedY = true; + } + } + } + onCustomOverScrolledBy(deltaY, clampedY); + } else { + int newScrollY = scrollY + deltaY; + final int top = -maxOverScrollY; + final int bottom = maxOverScrollY + scrollRangeY; - int newScrollY = scrollY + deltaY; - final int top = -maxOverScrollY; - final int bottom = maxOverScrollY + scrollRangeY; + boolean clampedY = false; + if (newScrollY > bottom) { + newScrollY = bottom; + clampedY = true; + } else if (newScrollY < top) { + newScrollY = top; + clampedY = true; + } - boolean clampedY = false; - if (newScrollY > bottom) { - newScrollY = bottom; - clampedY = true; - } else if (newScrollY < top) { - newScrollY = top; - clampedY = true; + onCustomOverScrolled(newScrollY, clampedY); } - - onCustomOverScrolled(newScrollY, clampedY); - - return clampedY; } /** @@ -1826,8 +2001,46 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } + /** + * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta + * would cause us to exceed the provided maximum overscroll, springs back instead. + * + * @param deltaY The (signed) number of pixels to scroll. + * @param clampedY Whether this value was clamped by the calling method, meaning we've reached + * the overscroll limit. + */ + private void onCustomOverScrolledBy(int deltaY, boolean clampedY) { + assert ANCHOR_SCROLLING; + mScrollAnchorViewY -= deltaY; + // Treat animating scrolls differently; see #computeScroll() for why. + if (!mScroller.isFinished()) { + if (clampedY) { + springBack(); + } else { + float overScrollTop = getCurrentOverScrollAmount(true /* top */); + if (isScrolledToTop() && mScrollAnchorViewY > 0) { + notifyOverscrollTopListener(mScrollAnchorViewY, + isRubberbanded(true /* onTop */)); + } else { + notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */)); + } + } + } + updateScrollAnchor(); + updateOnScrollChange(); + } + + /** + * Scrolls to the given position, overscrolling if needed. If called during a fling and the + * position exceeds the provided maximum overscroll, springs back instead. + * + * @param scrollY The target scroll position. + * @param clampedY Whether this value was clamped by the calling method, meaning we've reached + * the overscroll limit. + */ @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void onCustomOverScrolled(int scrollY, boolean clampedY) { + assert !ANCHOR_SCROLLING; // Treat animating scrolls differently; see #computeScroll() for why. if (!mScroller.isFinished()) { setOwnScrollY(scrollY); @@ -1846,27 +2059,51 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } + /** + * Springs back from an overscroll by stopping the {@link #mScroller} and animating the + * overscroll amount back to zero. + */ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void springBack() { - int scrollRange = getScrollRange(); - boolean overScrolledTop = mOwnScrollY <= 0; - boolean overScrolledBottom = mOwnScrollY >= scrollRange; - if (overScrolledTop || overScrolledBottom) { - boolean onTop; - float newAmount; - if (overScrolledTop) { - onTop = true; - newAmount = -mOwnScrollY; - setOwnScrollY(0); - mDontReportNextOverScroll = true; - } else { - onTop = false; - newAmount = mOwnScrollY - scrollRange; - setOwnScrollY(scrollRange); + if (ANCHOR_SCROLLING) { + boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0; + int maxPositiveScrollAmount = getMaxPositiveScrollAmount(); + boolean overscrolledBottom = maxPositiveScrollAmount < 0; + if (overScrolledTop || overscrolledBottom) { + float newAmount; + if (overScrolledTop) { + newAmount = mScrollAnchorViewY; + mScrollAnchorViewY = 0; + mDontReportNextOverScroll = true; + } else { + newAmount = -maxPositiveScrollAmount; + mScrollAnchorViewY -= maxPositiveScrollAmount; + } + setOverScrollAmount(newAmount, overScrolledTop, false); + setOverScrollAmount(0.0f, overScrolledTop, true); + mScroller.forceFinished(true); + } + } else { + int scrollRange = getScrollRange(); + boolean overScrolledTop = mOwnScrollY <= 0; + boolean overScrolledBottom = mOwnScrollY >= scrollRange; + if (overScrolledTop || overScrolledBottom) { + boolean onTop; + float newAmount; + if (overScrolledTop) { + onTop = true; + newAmount = -mOwnScrollY; + setOwnScrollY(0); + mDontReportNextOverScroll = true; + } else { + onTop = false; + newAmount = mOwnScrollY - scrollRange; + setOwnScrollY(scrollRange); + } + setOverScrollAmount(newAmount, onTop, false); + setOverScrollAmount(0.0f, onTop, true); + mScroller.forceFinished(true); } - setOverScrollAmount(newAmount, onTop, false); - setOverScrollAmount(0.0f, onTop, true); - mScroller.forceFinished(true); } } @@ -1971,6 +2208,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return null; } + private ExpandableNotificationRow getLastRowNotGone() { + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) { + return (ExpandableNotificationRow) child; + } + } + return null; + } + /** * @return the number of children which have visibility unequal to GONE */ @@ -2081,8 +2329,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateForwardAndBackwardScrollability() { - boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange(); - boolean backwardsScrollable = mScrollable && mOwnScrollY > 0; + boolean forwardScrollable = mScrollable && !isScrolledToBottom(); + boolean backwardsScrollable = mScrollable && !isScrolledToTop(); boolean changed = forwardScrollable != mForwardScrollable || backwardsScrollable != mBackwardScrollable; mForwardScrollable = forwardScrollable; @@ -2365,8 +2613,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd View child = getChildAt(i); if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { break; } else { lastChildBeforeGap = row; @@ -2384,8 +2631,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd View child = getChildAt(i); if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { return row; } } @@ -2403,18 +2649,24 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void fling(int velocityY) { if (getChildCount() > 0) { - int scrollRange = getScrollRange(); - float topAmount = getCurrentOverScrollAmount(true); float bottomAmount = getCurrentOverScrollAmount(false); if (velocityY < 0 && topAmount > 0) { - setOwnScrollY(mOwnScrollY - (int) topAmount); + if (ANCHOR_SCROLLING) { + mScrollAnchorViewY += topAmount; + } else { + setOwnScrollY(mOwnScrollY - (int) topAmount); + } mDontReportNextOverScroll = true; setOverScrollAmount(0, true, false); mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) * mOverflingDistance + topAmount; } else if (velocityY > 0 && bottomAmount > 0) { - setOwnScrollY((int) (mOwnScrollY + bottomAmount)); + if (ANCHOR_SCROLLING) { + mScrollAnchorViewY -= bottomAmount; + } else { + setOwnScrollY((int) (mOwnScrollY + bottomAmount)); + } setOverScrollAmount(0, false, false); mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(false /* onTop */) * mOverflingDistance @@ -2423,18 +2675,138 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // it will be set once we reach the boundary mMaxOverScroll = 0.0f; } - int minScrollY = Math.max(0, scrollRange); - if (mExpandedInThisMotion) { - minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); + if (ANCHOR_SCROLLING) { + flingScroller(velocityY); + } else { + int scrollRange = getScrollRange(); + int minScrollY = Math.max(0, scrollRange); + if (mExpandedInThisMotion) { + minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); + } + mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0, + mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); } - mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0, - mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); animateScroll(); } } /** + * Flings the overscroller with the given velocity (anchor-based scrolling). + * + * Because anchor-based scrolling can't track the current scroll position, the overscroller is + * always started at startY = 0, and we interpret the positions it computes as relative to the + * start of the scroll. + */ + private void flingScroller(int velocityY) { + assert ANCHOR_SCROLLING; + mIsScrollerBoundSet = false; + maybeFlingScroller(velocityY, true /* always fling */); + } + + private void maybeFlingScroller(int velocityY, boolean alwaysFling) { + assert ANCHOR_SCROLLING; + // Attempt to determine the maximum amount to scroll before we reach the end. + // If the first view is not materialized (for an upwards scroll) or the last view is either + // not materialized or is pinned to the shade (for a downwards scroll), we don't know this + // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update + // the scroller once we approach the start/end of the list. + int minY = Integer.MIN_VALUE; + int maxY = Integer.MAX_VALUE; + if (velocityY < 0) { + minY = getMaxNegativeScrollAmount(); + if (minY > Integer.MIN_VALUE) { + mIsScrollerBoundSet = true; + } + } else { + maxY = getMaxPositiveScrollAmount(); + if (maxY < Integer.MAX_VALUE) { + mIsScrollerBoundSet = true; + } + } + if (mIsScrollerBoundSet || alwaysFling) { + mLastScrollerY = 0; + // x velocity is set to 1 to avoid overscroller bug + mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0, + mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2); + } + } + + /** + * Returns the maximum number of pixels we can scroll in the positive direction (downwards) + * before reaching the bottom of the list (discounting overscroll). + * + * If the return value is negative then we have overscrolled; this is a transient state which + * should immediately be handled by adjusting the anchor position and adding the extra space to + * the bottom overscroll amount. + * + * If we don't know how many pixels we have left to scroll (because the last row has not been + * materialized, or it's in the shelf so it doesn't have its "natural" position), we return + * {@link Integer#MAX_VALUE}. + */ + private int getMaxPositiveScrollAmount() { + assert ANCHOR_SCROLLING; + // TODO: once we're recycling we need to check the adapter position of the last child. + ExpandableNotificationRow lastRow = getLastRowNotGone(); + if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) { + // distance from bottom of last child to bottom of notifications area is: + // distance from bottom of last child + return (int) (lastRow.getTranslationY() + lastRow.getActualHeight() + // to top of anchor view + - mScrollAnchorView.getTranslationY() + // plus distance from anchor view to top of notifications area + + mScrollAnchorViewY + // minus height of notifications area. + - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight())); + } else { + return Integer.MAX_VALUE; + } + } + + /** + * Returns the maximum number of pixels (as a negative number) we can scroll in the negative + * direction (upwards) before reaching the top of the list (discounting overscroll). + * + * If the return value is positive then we have overscrolled; this is a transient state which + * should immediately be handled by adjusting the anchor position and adding the extra space to + * the top overscroll amount. + * + * If we don't know how many pixels we have left to scroll (because the first row has not been + * materialized), we return {@link Integer#MIN_VALUE}. + */ + private int getMaxNegativeScrollAmount() { + assert ANCHOR_SCROLLING; + // TODO: once we're recycling we need to check the adapter position of the first child. + ExpandableView firstChild = getFirstChildNotGone(); + if (mScrollAnchorView != null && firstChild != null) { + // distance from top of first child to top of notifications area is: + // distance from top of anchor view + return (int) -(mScrollAnchorView.getTranslationY() + // to top of first child + - firstChild.getTranslationY() + // minus distance from top of anchor view to top of notifications area. + - mScrollAnchorViewY); + } else { + return Integer.MIN_VALUE; + } + } + + /** + * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view + * not being materialized or being pinned to the shelf, we need to check on every frame if we're + * able to set the bounds. If we are, we fling the scroller again with the newly computed + * bounds. + */ + private void maybeReflingScroller() { + if (!mIsScrollerBoundSet) { + // Because mScroller is a flywheel scroller, we fling with the minimum possible + // velocity to establish direction, so as not to perceptibly affect the velocity. + maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()), + false /* alwaysFling */); + } + } + + /** * @return Whether a fling performed on the top overscroll edge lead to the expanded * overScroll view (i.e QS). */ @@ -2485,25 +2857,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.COORDINATOR) - public int getFirstChildIntrinsicHeight() { - final ExpandableView firstChild = getFirstChildNotGone(); - int firstChildMinHeight = firstChild != null - ? firstChild.getIntrinsicHeight() - : mEmptyShadeView != null - ? mEmptyShadeView.getIntrinsicHeight() - : mCollapsedSize; - if (mOwnScrollY > 0) { - firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize); - } - return firstChildMinHeight; - } - - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getTopPaddingOverflow() { return mTopPaddingOverflow; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getPeekHeight() { final ExpandableView firstChild = getFirstChildNotGone(); @@ -2711,30 +3068,51 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollStateForRemovedChild(ExpandableView removedChild) { - int startingPosition = getPositionInLinearLayout(removedChild); - float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount(); - int padding; - if (increasedPaddingAmount >= 0) { - padding = (int) NotificationUtils.interpolate( - mPaddingBetweenElements, - mIncreasedPaddingBetweenElements, - increasedPaddingAmount); + if (ANCHOR_SCROLLING) { + if (removedChild == mScrollAnchorView) { + ExpandableView firstChild = getFirstChildNotGone(); + if (firstChild != null) { + mScrollAnchorView = firstChild; + } else { + mScrollAnchorView = mShelf; + } + // Adjust anchor view Y by the distance between the old and new anchors + // so that there's no visible change. + mScrollAnchorViewY += + mScrollAnchorView.getTranslationY() - removedChild.getTranslationY(); + } + updateScrollAnchor(); + // TODO: once we're recycling this will need to check the adapter position of the child + if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) { + mScrollAnchorViewY = 0; + } + updateOnScrollChange(); } else { - padding = (int) NotificationUtils.interpolate( - 0, - mPaddingBetweenElements, - 1.0f + increasedPaddingAmount); - } - int childHeight = getIntrinsicHeight(removedChild) + padding; - int endPosition = startingPosition + childHeight; - if (endPosition <= mOwnScrollY) { - // This child is fully scrolled of the top, so we have to deduct its height from the - // scrollPosition - setOwnScrollY(mOwnScrollY - childHeight); - } else if (startingPosition < mOwnScrollY) { - // This child is currently being scrolled into, set the scroll position to the start of - // this child - setOwnScrollY(startingPosition); + int startingPosition = getPositionInLinearLayout(removedChild); + float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount(); + int padding; + if (increasedPaddingAmount >= 0) { + padding = (int) NotificationUtils.interpolate( + mPaddingBetweenElements, + mIncreasedPaddingBetweenElements, + increasedPaddingAmount); + } else { + padding = (int) NotificationUtils.interpolate( + 0, + mPaddingBetweenElements, + 1.0f + increasedPaddingAmount); + } + int childHeight = getIntrinsicHeight(removedChild) + padding; + int endPosition = startingPosition + childHeight; + if (endPosition <= mOwnScrollY) { + // This child is fully scrolled of the top, so we have to deduct its height from the + // scrollPosition + setOwnScrollY(mOwnScrollY - childHeight); + } else if (startingPosition < mOwnScrollY) { + // This child is currently being scrolled into, set the scroll position to the + // start of this child + setOwnScrollY(startingPosition); + } } } @@ -2888,6 +3266,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd generateAddAnimation(child, false /* fromMoreCard */); updateAnimationState(child); updateChronometerForChild(child); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); + } + if (ANCHOR_SCROLLING) { + // TODO: once we're recycling this will need to check the adapter position of the child + if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) { + // New child was added at the top while we're scrolled to the top; + // make it the new anchor view so that we stay at the top. + mScrollAnchorView = child; + } + } } @ShadeViewRefactor(RefactorComponent.COORDINATOR) @@ -3381,17 +3770,24 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); if (vscroll != 0) { final int delta = (int) (vscroll * getVerticalScrollFactor()); - final int range = getScrollRange(); - int oldScrollY = mOwnScrollY; - int newScrollY = oldScrollY - delta; - if (newScrollY < 0) { - newScrollY = 0; - } else if (newScrollY > range) { - newScrollY = range; - } - if (newScrollY != oldScrollY) { - setOwnScrollY(newScrollY); - return true; + if (ANCHOR_SCROLLING) { + mScrollAnchorViewY -= delta; + updateScrollAnchor(); + clampScrollPosition(); + updateOnScrollChange(); + } else { + final int range = getScrollRange(); + int oldScrollY = mOwnScrollY; + int newScrollY = oldScrollY - delta; + if (newScrollY < 0) { + newScrollY = 0; + } else if (newScrollY > range) { + newScrollY = range; + } + if (newScrollY != oldScrollY) { + setOwnScrollY(newScrollY); + return true; + } } } } @@ -3459,12 +3855,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y; - int range = getScrollRange(); - if (mExpandedInThisMotion) { - range = Math.min(range, mMaxScrollAfterExpand); - } - float scrollAmount; + int range; + if (ANCHOR_SCROLLING) { + range = 0; // unused in the methods it's being passed to + } else { + range = getScrollRange(); + if (mExpandedInThisMotion) { + range = Math.min(range, mMaxScrollAfterExpand); + } + } if (deltaY < 0) { scrollAmount = overScrollDown(deltaY); } else { @@ -3501,9 +3901,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd onOverScrollFling(false, initialVelocity); } } else { - if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, - getScrollRange())) { - animateScroll(); + if (ANCHOR_SCROLLING) { + // TODO + } else { + if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, + getScrollRange())) { + animateScroll(); + } } } } @@ -3515,8 +3919,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { - if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { - animateScroll(); + if (ANCHOR_SCROLLING) { + // TODO + } else { + if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, + getScrollRange())) { + animateScroll(); + } } mActivePointerId = INVALID_POINTER; endDrag(); @@ -3585,12 +3994,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } - @ShadeViewRefactor(RefactorComponent.INPUT) - private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { - ev.offsetLocation(sourceView.getX(), sourceView.getY()); - ev.offsetLocation(-targetView.getX(), -targetView.getY()); - } - @Override @ShadeViewRefactor(RefactorComponent.INPUT) public boolean onInterceptTouchEvent(MotionEvent ev) { @@ -3763,8 +4166,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd setIsBeingDragged(false); mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); - if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { - animateScroll(); + if (ANCHOR_SCROLLING) { + // TODO + } else { + if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { + animateScroll(); + } } break; case MotionEvent.ACTION_POINTER_UP: @@ -3839,14 +4246,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: // fall through case android.R.id.accessibilityActionScrollUp: - final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop - - mShelf.getIntrinsicHeight(); - final int targetScrollY = Math.max(0, - Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); - if (targetScrollY != mOwnScrollY) { - mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY); - animateScroll(); - return true; + if (ANCHOR_SCROLLING) { + // TODO + } else { + final int viewportHeight = + getHeight() - mPaddingBottom - mTopPadding - mPaddingTop + - mShelf.getIntrinsicHeight(); + final int targetScrollY = Math.max(0, + Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); + if (targetScrollY != mOwnScrollY) { + mScroller.startScroll(mScrollX, mOwnScrollY, 0, + targetScrollY - mOwnScrollY); + animateScroll(); + return true; + } } break; } @@ -3905,13 +4318,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override @ShadeViewRefactor(RefactorComponent.COORDINATOR) public boolean isScrolledToTop() { - return mOwnScrollY == 0; + if (ANCHOR_SCROLLING) { + updateScrollAnchor(); + // TODO: once we're recycling this will need to check the adapter position of the child + return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0; + } else { + return mOwnScrollY == 0; + } } @Override @ShadeViewRefactor(RefactorComponent.COORDINATOR) public boolean isScrolledToBottom() { - return mOwnScrollY >= getScrollRange(); + if (ANCHOR_SCROLLING) { + return getMaxPositiveScrollAmount() <= 0; + } else { + return mOwnScrollY >= getScrollRange(); + } } @Override @@ -3953,7 +4376,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd resetCheckSnoozeLeavebehind(); mAmbientState.setExpansionChanging(false); if (!mIsExpanded) { - setOwnScrollY(0); + resetScrollPosition(); mStatusBar.resetUserExpandedStates(); clearTemporaryViews(); clearUserLockedViews(); @@ -4012,7 +4435,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void resetScrollPosition() { mScroller.abortAnimation(); - setOwnScrollY(0); + if (ANCHOR_SCROLLING) { + // TODO: once we're recycling this will need to modify the adapter position instead + mScrollAnchorView = getFirstChildNotGone(); + mScrollAnchorViewY = 0; + updateOnScrollChange(); + } else { + setOwnScrollY(0); + } } @ShadeViewRefactor(RefactorComponent.COORDINATOR) @@ -4081,6 +4511,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private void updateScrollPositionOnExpandInBottom(ExpandableView view) { if (view instanceof ExpandableNotificationRow && !onKeyguard()) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; + // TODO: once we're recycling this will need to check the adapter position of the child if (row.isUserLocked() && row != getFirstChildNotGone()) { if (row.isSummaryWithChildren()) { return; @@ -4098,7 +4529,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; } if (endPosition > layoutEnd) { - setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); + if (ANCHOR_SCROLLING) { + mScrollAnchorViewY -= (endPosition - layoutEnd); + updateScrollAnchor(); + updateOnScrollChange(); + } else { + setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); + } mDisallowScrollingInThisMotion = true; } } @@ -4663,9 +5100,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd super.onInitializeAccessibilityEventInternal(event); event.setScrollable(mScrollable); event.setScrollX(mScrollX); - event.setScrollY(mOwnScrollY); event.setMaxScrollX(mScrollX); - event.setMaxScrollY(getScrollRange()); + if (ANCHOR_SCROLLING) { + // TODO + } else { + event.setScrollY(mOwnScrollY); + event.setMaxScrollY(getScrollRange()); + } } @Override @@ -4850,13 +5291,63 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.COORDINATOR) - public void setOwnScrollY(int ownScrollY) { + private void setOwnScrollY(int ownScrollY) { + assert !ANCHOR_SCROLLING; if (ownScrollY != mOwnScrollY) { // We still want to call the normal scrolled changed for accessibility reasons onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); mOwnScrollY = ownScrollY; - updateForwardAndBackwardScrollability(); - requestChildrenUpdate(); + updateOnScrollChange(); + } + } + + private void updateOnScrollChange() { + updateForwardAndBackwardScrollability(); + requestChildrenUpdate(); + } + + private void updateScrollAnchor() { + int anchorIndex = indexOfChild(mScrollAnchorView); + // If the anchor view has been scrolled off the top, move to the next view. + while (mScrollAnchorViewY < 0) { + View nextAnchor = null; + for (int i = anchorIndex + 1; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE + && child instanceof ExpandableNotificationRow) { + anchorIndex = i; + nextAnchor = child; + break; + } + } + if (nextAnchor == null) { + break; + } + mScrollAnchorViewY += + (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY()); + mScrollAnchorView = nextAnchor; + } + // If the view above the anchor view is fully visible, make it the anchor view. + while (anchorIndex > 0 && mScrollAnchorViewY > 0) { + View prevAnchor = null; + for (int i = anchorIndex - 1; i >= 0; i--) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE + && child instanceof ExpandableNotificationRow) { + anchorIndex = i; + prevAnchor = child; + break; + } + } + if (prevAnchor == null) { + break; + } + float distanceToPreviousAnchor = + mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY(); + if (distanceToPreviousAnchor < mScrollAnchorViewY) { + mScrollAnchorViewY -= (int) distanceToPreviousAnchor; + mScrollAnchorView = prevAnchor; + } } } @@ -4872,6 +5363,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mAmbientState.setShelf(shelf); mStateAnimator.setShelf(shelf); shelf.bind(mAmbientState, this); + if (ANCHOR_SCROLLING) { + mScrollAnchorView = mShelf; + } } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -5276,11 +5770,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd currentIndex++; boolean beforeSpeedBump; if (mLowPriorityBeforeSpeedBump) { - beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient( - row.getStatusBarNotification().getKey()); + beforeSpeedBump = !row.getEntry().ambient; } else { - beforeSpeedBump = mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification()); + beforeSpeedBump = row.getEntry().isHighPriority(); } if (beforeSpeedBump) { speedBumpIndex = currentIndex; @@ -5304,8 +5796,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd continue; } ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { if (currentIndex > 0) { gapIndex = currentIndex; } @@ -5835,7 +6326,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) { boolean isValidDirection; if (NotificationUtils.useNewInterruptionModel(mContext)) { - isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown; + isValidDirection = mDismissRtl ? !isRightOrDown : isRightOrDown; } else { isValidDirection = true; } @@ -6022,7 +6513,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public void expansionStateChanged(boolean isExpanding) { mExpandingNotification = isExpanding; if (!mExpandedInThisMotion) { - mMaxScrollAfterExpand = mOwnScrollY; + if (ANCHOR_SCROLLING) { + // TODO + } else { + mMaxScrollAfterExpand = mOwnScrollY; + } mExpandedInThisMotion = true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 25fb7f9197e4..2a8808021715 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -41,6 +41,8 @@ import java.util.List; */ public class StackScrollAlgorithm { + static final boolean ANCHOR_SCROLLING = false; + private static final String LOG_TAG = "StackScrollAlgorithm"; private final ViewGroup mHostView; @@ -236,6 +238,10 @@ public class StackScrollAlgorithm { scrollY = Math.max(0, scrollY); state.scrollY = (int) (scrollY + bottomOverScroll); + if (ANCHOR_SCROLLING) { + state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll); + } + //now init the visible children and update paddings int childCount = hostView.getChildCount(); state.visibleChildren.clear(); @@ -252,6 +258,11 @@ public class StackScrollAlgorithm { // iterating over it again, it's filled with the actual resolved value. for (int i = 0; i < childCount; i++) { + if (ANCHOR_SCROLLING) { + if (i == ambientState.getAnchorViewIndex()) { + state.anchorViewIndex = state.visibleChildren.size(); + } + } ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { if (v == ambientState.getShelf()) { @@ -350,28 +361,74 @@ public class StackScrollAlgorithm { private void updatePositionsForState(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { - // The y coordinate of the current child. - float currentYPosition = -algorithmState.scrollY; - int childCount = algorithmState.visibleChildren.size(); - for (int i = 0; i < childCount; i++) { - currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition); + if (ANCHOR_SCROLLING) { + float currentYPosition = algorithmState.anchorViewY; + int childCount = algorithmState.visibleChildren.size(); + for (int i = algorithmState.anchorViewIndex; i < childCount; i++) { + if (i > algorithmState.anchorViewIndex && ambientState.beginsNewSection(i)) { + currentYPosition += mGapHeight; + } + currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition, + false /* reverse */); + } + currentYPosition = algorithmState.anchorViewY; + for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) { + if (ambientState.beginsNewSection(i + 1)) { + currentYPosition -= mGapHeight; + } + currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition, + true /* reverse */); + } + } else { + // The y coordinate of the current child. + float currentYPosition = -algorithmState.scrollY; + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + if (ambientState.beginsNewSection(i)) { + currentYPosition += mGapHeight; + } + currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition, + false /* reverse */); + } } } + /** + * Populates the {@link ExpandableViewState} for a single child. + * + * @param i The index of the child in + * {@link StackScrollAlgorithmState#visibleChildren}. + * @param algorithmState The overall output state of the algorithm. + * @param ambientState The input state provided to the algorithm. + * @param currentYPosition The Y position of the current pass of the algorithm. For a forward + * pass, this should be the top of the child; for a reverse pass, the + * bottom of the child. + * @param reverse Whether we're laying out children in the reverse direction (Y + * positions + * decreasing) instead of the forward direction (Y positions + * increasing). + * @return The Y position after laying out the child. This will be the {@code currentYPosition} + * for the next call to this method, after adjusting for any gaps between children. + */ protected float updateChild( int i, StackScrollAlgorithmState algorithmState, AmbientState ambientState, - float currentYPosition) { + float currentYPosition, + boolean reverse) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; int paddingAfterChild = getPaddingAfterChild(algorithmState, child); int childHeight = getMaxAllowedChildHeight(child); - if (ambientState.beginsNewSection(i)) { - currentYPosition += mGapHeight; + if (reverse) { + childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild); + if (currentYPosition <= 0) { + childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; + } + } else { + childViewState.yTranslation = currentYPosition; } - childViewState.yTranslation = currentYPosition; boolean isFooterView = child instanceof FooterView; boolean isEmptyShadeView = child instanceof EmptyShadeView; @@ -396,9 +453,13 @@ public class StackScrollAlgorithm { clampPositionToShelf(child, childViewState, ambientState); } - currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild; - if (currentYPosition <= 0) { - childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; + if (reverse) { + currentYPosition = childViewState.yTranslation; + } else { + currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild; + if (currentYPosition <= 0) { + childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; + } } if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); @@ -464,6 +525,7 @@ public class StackScrollAlgorithm { // To check if the row need to do translation according to scroll Y // heads up show full of row's content and any scroll y indicate that the // translationY need to move up the HUN. + // TODO: fix this check for anchor scrolling. if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) { childState.yTranslation -= ambientState.getScrollY(); } @@ -607,10 +669,18 @@ public class StackScrollAlgorithm { public class StackScrollAlgorithmState { /** - * The scroll position of the algorithm + * The scroll position of the algorithm (absolute scrolling). */ public int scrollY; + /** The index of the anchor view (anchor scrolling). */ + public int anchorViewIndex; + + /** + * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling). + */ + public int anchorViewY; + /** * The children from the host view which are not gone. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index fac4dbbe735a..b96c55b37c48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -168,8 +168,8 @@ public class AutoTileManager { @Override public void onAutoModeChanged(int autoMode) { - if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM - || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) { + if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME + || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { addNightTile(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 03375d20e6db..e953ad53527b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -113,6 +113,7 @@ public class KeyguardStatusBarView extends RelativeLayout * Ratio representing being in ambient mode or not. */ private float mDarkAmount; + private boolean mDozing; public KeyguardStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -210,7 +211,7 @@ public class KeyguardStatusBarView extends RelativeLayout mMultiUserSwitch.setVisibility(View.GONE); } } - mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable); + mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable || mDozing); } private void updateSystemIconsLayoutParams() { @@ -347,7 +348,7 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); onThemeChanged(); - updateDozeState(); + updateDarkState(); } @Override @@ -506,21 +507,29 @@ public class KeyguardStatusBarView extends RelativeLayout } } + public void setDozing(boolean dozing) { + if (mDozing == dozing) { + return; + } + mDozing = dozing; + updateVisibilities(); + } + public void setDarkAmount(float darkAmount) { mDarkAmount = darkAmount; if (darkAmount == 0) { dozeTimeTick(); } - updateDozeState(); + updateDarkState(); } public void dozeTimeTick() { mCurrentBurnInOffsetX = getBurnInOffset(mBurnInOffset, true /* xAxis */); mCurrentBurnInOffsetY = getBurnInOffset(mBurnInOffset, false /* xAxis */); - updateDozeState(); + updateDarkState(); } - private void updateDozeState() { + private void updateDarkState() { float alpha = 1f - mDarkAmount; int visibility = alpha != 0f ? VISIBLE : INVISIBLE; mCarrierLabel.setAlpha(alpha * alpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index d3c6a1d8ee74..ee047e41ec78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -243,6 +243,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0); } mAccessibilityManagerWrapper.addCallback(mAccessibilityListener); + + // Respect the latest disabled-flags. + mCommandQueue.recomputeDisableFlags(mDisplayId, false); } @Override @@ -799,7 +802,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } private void onAccessibilityClick(View v) { - mAccessibilityManager.notifyAccessibilityButtonClicked(); + final Display display = v.getDisplay(); + mAccessibilityManager.notifyAccessibilityButtonClicked( + display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY); } private boolean onAccessibilityLongClick(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index a5d938216f5c..39fbbb17c498 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; +import static com.android.systemui.statusbar.phone.NavigationPrototypeController.PROTOTYPE_ENABLED; import android.annotation.NonNull; import android.content.Context; @@ -84,7 +85,7 @@ public abstract class NavigationGestureAction { // Tell launcher that this action requires a stable task list or not boolean flag = requiresStableTaskList(); - if (flag != sLastTaskStabilizationFlag) { + if (getGlobalBoolean(PROTOTYPE_ENABLED) && flag != sLastTaskStabilizationFlag) { Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0); sLastTaskStabilizationFlag = flag; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index a09e5858d576..f762a6a68ad6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -37,6 +37,7 @@ public class NavigationPrototypeController extends ContentObserver { private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; + public static final String PROTOTYPE_ENABLED = "prototype_enabled"; @Retention(RetentionPolicy.SOURCE) @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 077fcda70f0c..e86996a81bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -194,8 +194,7 @@ public class NotificationIconAreaController implements DarkReceiver, if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) { return false; } - if (!showLowPriority - && !mEntryManager.getNotificationData().isHighPriority(entry.notification)) { + if (!showLowPriority && !entry.isHighPriority()) { return false; } if (!entry.isTopLevelChild()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 0d5ebb9b6578..c10837165c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -140,7 +140,8 @@ public class NotificationPanelView extends PanelView implements private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardUserSwitcher mKeyguardUserSwitcher; - private KeyguardStatusBarView mKeyguardStatusBar; + @VisibleForTesting + protected KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; private QS mQs; private FrameLayout mQsFrame; @@ -2792,6 +2793,7 @@ public class NotificationPanelView extends PanelView implements if (mDozing) { mNotificationStackScroller.setShowDarkShelf(!hasCustomClock()); } + mKeyguardStatusBar.setDozing(mDozing); if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 43c35f11d9e5..18711c0d1ae3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -173,7 +173,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mProvisionedController = Dependency.get(DeviceProvisionedController.class); mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mLocationController = Dependency.get(LocationController.class); - mPrivacyItemController = new PrivacyItemController(mContext, this); + mPrivacyItemController = Dependency.get(PrivacyItemController.class); mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot); @@ -266,7 +266,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mNextAlarmController.addCallback(mNextAlarmCallback); mDataSaver.addCallback(this); mKeyguardMonitor.addCallback(this); - mPrivacyItemController.setListening(true); + mPrivacyItemController.addCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); @@ -294,7 +294,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mNextAlarmController.removeCallback(mNextAlarmCallback); mDataSaver.removeCallback(this); mKeyguardMonitor.removeCallback(this); - mPrivacyItemController.setListening(false); + mPrivacyItemController.removeCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this); mContext.unregisterReceiver(mIntentReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 4f61009095c7..86326beb8b00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -64,6 +64,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -304,8 +305,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); final int rank = mEntryManager.getNotificationData().getRank(notificationKey); + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation( + mEntryManager.getNotificationData().get(notificationKey)); final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, - rank, count, true); + rank, count, true, location); try { mBarService.onNotificationClick(notificationKey, nv); } catch (RemoteException ex) { 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 9c4db34b4f76..7881df97df2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -188,6 +188,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); v.mController = controller; v.mEntry = entry; + v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser())); v.setTag(VIEW_TAG); return v; @@ -298,7 +299,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (mWrapper != null) { mWrapper.setRemoteInputVisible(true); } - mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser())); mEditText.setInnerFocusable(true); mEditText.mShowImeOnInputConnection = true; mEditText.setText(mEntry.remoteInputText); @@ -328,7 +328,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mResetting = true; mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); - mEditText.setTextOperationUser(null); mEditText.getText().clear(); mEditText.setEnabled(true); mSendButton.setVisibility(VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java new file mode 100644 index 000000000000..fa4b3fe4be18 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.usb; + +import android.content.DialogInterface; +import android.content.Intent; +import android.hardware.usb.ParcelableUsbPort; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbPort; +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; +import com.android.systemui.R; + +/** + * Activity that alerts the user when contaminant is detected on USB port. + */ +public class UsbContaminantActivity extends AlertActivity + implements DialogInterface.OnClickListener { + private static final String TAG = "UsbContaminantActivity"; + + private UsbDisconnectedReceiver mDisconnectedReceiver; + private UsbPort mUsbPort; + + @Override + public void onCreate(Bundle icicle) { + Window window = getWindow(); + window.addSystemFlags(WindowManager.LayoutParams + .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + + super.onCreate(icicle); + + Intent intent = getIntent(); + ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); + mUsbPort = port.getUsbPort(getSystemService(UsbManager.class)); + + final AlertController.AlertParams ap = mAlertParams; + ap.mTitle = getString(R.string.usb_contaminant_title); + ap.mMessage = getString(R.string.usb_contaminant_message); + ap.mPositiveButtonText = getString(android.R.string.ok); + ap.mPositiveButtonListener = this; + + setupAlert(); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams params) { + super.onWindowAttributesChanged(params); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 60a20cf15cc0..e80275793b28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -61,7 +61,6 @@ public class BubbleControllerTest extends SysuiTestCase { private IActivityManager mActivityManager; @Mock private DozeParameters mDozeParameters; - @Mock private FrameLayout mStatusBarView; @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @@ -80,6 +79,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mStatusBarView = new FrameLayout(mContext); mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); // Bubbles get added to status bar window view diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java new file mode 100644 index 000000000000..1bb7ef4a657b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Resources; +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private ExpandedAnimationController mExpandedController = new ExpandedAnimationController(); + + private int mStackOffset; + private float mBubblePadding; + private float mBubbleSize; + + private PointF mExpansionPoint; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mExpandedController); + Resources res = mLayout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + } + + @Test + public void testExpansionAndCollapse() throws InterruptedException { + mExpansionPoint = new PointF(100, 100); + Runnable afterExpand = Mockito.mock(Runnable.class); + mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testExpanded(); + Mockito.verify(afterExpand).run(); + + Runnable afterCollapse = Mockito.mock(Runnable.class); + mExpandedController.collapseBackToStack(afterCollapse); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); + Mockito.verify(afterExpand).run(); + } + + /** Check that children are in the correct positions for being stacked. */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** Check that children are in the correct positions for being expanded. */ + private void testExpanded() { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)), + mViews.get(i).getTranslationX(), + 2f); + assertEquals(mBubblePadding + mCutoutInsetSize, + mViews.get(i).getTranslationY(), 2f); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java new file mode 100644 index 000000000000..bfc02d902416 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; + +import android.os.SystemClock; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */ +public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { + static final float TEST_TRANSLATION_X_OFFSET = 15f; + + @Spy + private TestableAnimationController mTestableController = new TestableAnimationController(); + + @Before + public void setUp() throws Exception { + super.setUp(); + + // By default, use translation animations, chain the X animations with the default + // offset, and don't actually remove views immediately (since most implementations will wait + // to animate child views out before actually removing them). + mTestableController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X)); + mTestableController.setOffsetForProperty( + DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET); + mTestableController.setRemoveImmediately(false); + } + + @Test + public void testRenderVisibility() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // The last child should be GONE, the rest VISIBLE. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, + mLayout.getChildAt(i).getVisibility()); + } + } + + @Test + public void testHierarchyChanges() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Make sure the controller was notified of all the views we added. + for (View mView : mViews) { + Mockito.verify(mTestableController).onChildAdded(mView, 0); + } + + // Remove some views and ensure the controller was notified, with the proper indices. + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + mLayout.removeView(mViews.get(2)); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(1)), eq(1), any()); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(2)), eq(1), any()); + + // Make sure we still get view added notifications after doing some removals. + final View newBubble = new FrameLayout(mContext); + mLayout.addView(newBubble, 0); + Mockito.verify(mTestableController).onChildAdded(newBubble, 0); + } + + @Test + public void testUpdateValueNotChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Don't chain any values. + mTestableController.setChainedProperties(Sets.newHashSet()); + + // Child views should not be translated. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + // Animate the first child's translation X. + final CountDownLatch animLatch = new CountDownLatch(1); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100, + animLatch::countDown); + animLatch.await(1, TimeUnit.SECONDS); + + // Ensure that the first view has been translated, but not the second one. + assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + } + + @Test + public void testUpdateValueXChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + testChainedTranslationAnimations(); + } + + @Test + public void testSetEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + final CountDownLatch yLatch = new CountDownLatch(1); + final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + yLatch.countDown(); + } + }); + + // Set end listeners for both x and y. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + yLatch.await(1, TimeUnit.SECONDS); + + // Make sure the x end listener was called only one time, and the y listener was never + // called since we didn't animate y. Wait 1 second after the original animation end trigger + // to make sure it doesn't get called again. + Mockito.verify(xEndListener, Mockito.after(1000).times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + Mockito.verify(yEndListener, Mockito.after(1000).never()) + .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat()); + } + + @Test + public void testRemoveEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + // Set the end listener. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + + InOrder endListenerCalls = inOrder(xEndListener); + endListenerCalls.verify(xEndListener, Mockito.times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + + // Animate X again, remove the end listener. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + xLatch.await(1, TimeUnit.SECONDS); + + // Make sure the end listener was not called. + endListenerCalls.verifyNoMoreInteractions(); + } + + @Test + public void testPrecedingNonRemovedIndex() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Call removeView at index 4, but don't actually remove it yet (as if we're animating it + // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since + // 4 is on its way out. + assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); + mLayout.removeView(mViews.get(4)); + assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); + + // Call removeView at index 1, and actually remove it immediately. With the old view at 1 + // instantly gone, the preceding view to 0 should be 1 in both cases. + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + } + + @Test + public void testSetController() throws InterruptedException { + // Add the bubbles, then set the controller, to make sure that a controller added to an + // already-initialized view works correctly. + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mTestableController); + testChainedTranslationAnimations(); + + TestableAnimationController secondController = + Mockito.spy(new TestableAnimationController()); + secondController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y)); + secondController.setChainedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X)); + secondController.setOffsetForProperty( + DynamicAnimation.SCALE_X, 10f); + secondController.setRemoveImmediately(true); + + mLayout.setController(secondController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, + 0, + 1.5f); + + waitForPropertyAnimations(DynamicAnimation.SCALE_X); + + // Make sure we never asked the original controller about any SCALE animations, that would + // mean the controller wasn't switched over properly. + Mockito.verify(mTestableController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(mTestableController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + // Make sure we asked the new controller about its animated properties, and configuration + // options. + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getAnimatedProperties(); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + mLayout.setController(mTestableController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Make sure we never asked the second controller about the TRANSLATION_X animation. + Mockito.verify(secondController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt()); + Mockito.verify(secondController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X)); + + } + + @Test + public void testArePropertiesAnimating() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + // Wait for the animations to get underway. + SystemClock.sleep(50); + + assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X)); + assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y)); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + } + + @Test + public void testCancelAllAnimations() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 1000); + + mLayout.cancelAllAnimations(); + + waitForLayoutMessageQueue(); + + // Animations should be somewhere before their end point. + assertTrue(mViews.get(0).getTranslationX() < 1000); + assertTrue(mViews.get(0).getTranslationY() < 1000); + } + + + /** Standard test of chained translation animations. */ + private void testChainedTranslationAnimations() throws InterruptedException { + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Since we enabled chaining, animating the first view to 100 should animate the second to + // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble + // not being visible, or animated, make sure that it has the appropriate chained + // translation. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals( + 100 + i * TEST_TRANSLATION_X_OFFSET, + mLayout.getChildAt(i).getTranslationX(), .1f); + } + + // Ensure that the Y translations were unaffected. + assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + + // Animate the first child's Y translation. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y); + + // Ensure that only the first view's Y translation chained, since we only chained X + // translations. + assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + } + + /** + * Animation controller with configuration methods whose return values can be set by individual + * tests. + */ + private class TestableAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>(); + private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>(); + private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>(); + private boolean mRemoveImmediately = false; + + void setAnimatedProperties( + Set<DynamicAnimation.ViewProperty> animatedProperties) { + mAnimatedProperties = animatedProperties; + } + + void setChainedProperties( + Set<DynamicAnimation.ViewProperty> chainedProperties) { + mChainedProperties = chainedProperties; + } + + void setOffsetForProperty( + DynamicAnimation.ViewProperty property, float offset) { + mOffsetForProperty.put(property, offset); + } + + public void setRemoveImmediately(boolean removeImmediately) { + mRemoveImmediately = removeImmediately; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return mAnimatedProperties; + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return mChainedProperties.contains(property) ? index + 1 : NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return mOffsetForProperty.getOrDefault(property, 0f); + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce(); + } + + @Override + void onChildAdded(View child, int index) {} + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + if (mRemoveImmediately) { + actuallyRemove.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java new file mode 100644 index 000000000000..186a76219536 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.DisplayCutout; +import android.view.View; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a + * testable version of the layout, and provides some helpful methods to add views to the layout and + * wait for physics animations to finish running. + * + * See physics-animation-testing.md. + */ +public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { + TestablePhysicsAnimationLayout mLayout; + List<View> mViews = new ArrayList<>(); + + Handler mMainThreadHandler; + + int mMaxRenderedBubbles; + int mSystemWindowInsetSize = 50; + int mCutoutInsetSize = 100; + + int mWidth = 1000; + int mHeight = 1000; + + @Mock + private WindowInsets mWindowInsets; + + @Mock + private DisplayCutout mCutout; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mLayout = new TestablePhysicsAnimationLayout(mContext); + mLayout.setLeft(0); + mLayout.setRight(mWidth); + mLayout.setTop(0); + mLayout.setBottom(mHeight); + + mMaxRenderedBubbles = + getContext().getResources().getInteger(R.integer.bubbles_max_rendered); + mMainThreadHandler = new Handler(Looper.getMainLooper()); + + when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize); + + when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout); + when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize); + } + + /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ + void addOneMoreThanRenderLimitBubbles() { + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + final View newView = new FrameLayout(mContext); + mLayout.addView(newView, 0); + mViews.add(0, newView); + + newView.setTranslationX(0); + newView.setTranslationY(0); + } + } + + /** + * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties' + * animations to finish before allowing the test to proceed. + */ + void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties) + throws InterruptedException { + final CountDownLatch animLatch = new CountDownLatch(properties.length); + for (DynamicAnimation.ViewProperty property : properties) { + mLayout.setTestEndListenerForProperty(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + animLatch.countDown(); + } + }, property); + } + animLatch.await(1, TimeUnit.SECONDS); + } + + /** Uses a latch to wait for the message queue to finish. */ + void waitForLayoutMessageQueue() throws InterruptedException { + // Wait for layout, then the view should be actually removed. + CountDownLatch layoutLatch = new CountDownLatch(1); + mLayout.post(layoutLatch::countDown); + layoutLatch.await(1, TimeUnit.SECONDS); + } + + /** + * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations + * are run on the main thread, which is a requirement of DynamicAnimation. + */ + protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout { + public TestablePhysicsAnimationLayout(Context context) { + super(context); + } + + @Override + public void setController(PhysicsAnimationController controller) { + mMainThreadHandler.post(() -> super.setController(controller)); + try { + waitForLayoutMessageQueue(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void cancelAllAnimations() { + mMainThreadHandler.post(super::cancelAllAnimations); + } + + @Override + protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property, + int index, float value, float startVel, Runnable after) { + mMainThreadHandler.post(() -> + super.animateValueForChildAtIndex(property, index, value, startVel, after)); + } + + @Override + public WindowInsets getRootWindowInsets() { + return mWindowInsets; + } + + /** + * Sets an end listener that will be called after the 'real' end listener that was already + * set. + */ + private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + final DynamicAnimation.OnAnimationEndListener realEndListener = + mEndListenerForProperty.get(property); + + setEndListenerForProperty((animation, canceled, value, velocity) -> { + if (realEndListener != null) { + realEndListener.onAnimationEnd(animation, canceled, value, velocity); + } + + listener.onAnimationEnd(animation, canceled, value, velocity); + }, property); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java new file mode 100644 index 000000000000..db819d57417b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private TestableStackController mStackController = new TestableStackController(); + + private int mStackOffset; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mStackController); + mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); + } + + /** + * Test moving around the stack, and make sure the position is updated correctly, and the stack + * direction is correct. + */ + @Test + public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { + mStackController.moveFirstBubbleWithStackFollowing(200, 100); + + // The first bubble should have moved instantly, the rest should be waiting for animation. + assertEquals(200, mViews.get(0).getTranslationX(), .1f); + assertEquals(100, mViews.get(0).getTranslationY(), .1f); + assertEquals(0, mViews.get(1).getTranslationX(), .1f); + assertEquals(0, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack got moved to the right place and is stacked to the left. + testStackedAtPosition(200, 100, -1); + assertEquals(new PointF(200, 100), mStackController.getStackPosition()); + + mStackController.moveFirstBubbleWithStackFollowing(1000, 500); + + // The first bubble again should have moved instantly while the rest remained where they + // were until the animation takes over. + assertEquals(1000, mViews.get(0).getTranslationX(), .1f); + assertEquals(500, mViews.get(0).getTranslationY(), .1f); + assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f); + assertEquals(100, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + testStackedAtPosition(1000, 500, 1); + assertEquals(new PointF(1000, 500), mStackController.getStackPosition()); + } + + @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") + public void testFlingSideways() throws InterruptedException { + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 5000f, 1.15f, new SpringForce(), mWidth * 1f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 0f, 1.15f, new SpringForce(), 0f); + + // Nothing should move initially since the animations haven't begun, including the first + // view. + assertEquals(0f, mViews.get(0).getTranslationX(), 1f); + assertEquals(0f, mViews.get(0).getTranslationY(), 1f); + + // Wait for the flinging. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Wait for the springing. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung all the way to the right side, with the + // stack stacked off to the right now. + testStackedAtPosition(mWidth * 1f, 0f, 1); + } + + @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") + public void testFlingUpFromBelowBottomCenter() throws InterruptedException { + // Move to the center of the screen, just past the bottom. + mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 0, 1.15f, new SpringForce(), 27f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 5000f, 1.15f, new SpringForce(), 27f); + + // Nothing should move initially since the animations haven't begun. + assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f); + assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung a bit but then sprung to the final + // destination which is (27, 27). + testStackedAtPosition(27, 27, -1); + } + + @Test + public void testChildAdded() throws InterruptedException { + // Move the stack to y = 500. + mStackController.moveFirstBubbleWithStackFollowing(0f, 500f); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + final View newView = new FrameLayout(mContext); + mLayout.addView( + newView, + 0, + new FrameLayout.LayoutParams(50, 50)); + + waitForPropertyAnimations( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.SCALE_X, + DynamicAnimation.SCALE_Y); + + // The new view should be at the top of the stack, in the correct position. + assertEquals(0f, newView.getTranslationX(), .1f); + assertEquals(500f, newView.getTranslationY(), .1f); + assertEquals(1f, newView.getScaleX(), .1f); + assertEquals(1f, newView.getScaleY(), .1f); + assertEquals(1f, newView.getAlpha(), .1f); + } + + @Test + public void testChildRemoved() throws InterruptedException { + final View firstView = mLayout.getChildAt(0); + mLayout.removeView(firstView); + + // The view should still be there, since the controller is animating it out and hasn't yet + // actually removed it from the parent view. + assertEquals(0, mLayout.indexOfChild(firstView)); + + waitForPropertyAnimations(DynamicAnimation.ALPHA); + waitForLayoutMessageQueue(); + + assertEquals(-1, mLayout.indexOfChild(firstView)); + + // The subsequent view should have been translated over to 0, not stacked off to the left. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + } + + /** + * Checks every child view to make sure it's stacked at the given coordinates, off to the left + * or right side depending on offset multiplier. + */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** + * Testable version of the stack controller that dispatches its animations on the main thread. + */ + private class TestableStackController extends StackAnimationController { + @Override + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float vel, float friction, + SpringForce spring, Float finalPosition) { + mMainThreadHandler.post(() -> + super.flingThenSpringFirstBubbleWithStackFollowing( + property, vel, friction, spring, finalPosition)); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index e6d7ee7407d4..98bf3c2743a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -45,9 +45,12 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -73,6 +76,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var userManager: UserManager @Captor private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> + @Captor + private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback> private lateinit var testableLooper: TestableLooper private lateinit var privacyItemController: PrivacyItemController @@ -95,12 +100,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { } })).`when`(userManager).getProfiles(anyInt()) - privacyItemController = PrivacyItemController(mContext, callback) + privacyItemController = PrivacyItemController(mContext) } @Test - fun testSetListeningTrue() { - privacyItemController.setListening(true) + fun testSetListeningTrueByAddingCallback() { + privacyItemController.addCallback(callback) verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any(AppOpsController.Callback::class.java)) testableLooper.processAllMessages() @@ -108,6 +113,13 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test + fun testSetListeningTrue() { + privacyItemController.setListening(true) + verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), + any(AppOpsController.Callback::class.java)) + } + + @Test fun testSetListeningFalse() { privacyItemController.setListening(true) privacyItemController.setListening(false) @@ -121,7 +133,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) - privacyItemController.setListening(true) + privacyItemController.addCallback(callback) testableLooper.processAllMessages() verify(callback).privacyChanged(capture(argCaptor)) assertEquals(1, argCaptor.value.size) @@ -131,7 +143,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testSystemApps() { doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME, 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) - privacyItemController.setListening(true) + privacyItemController.addCallback(callback) testableLooper.processAllMessages() verify(callback).privacyChanged(capture(argCaptor)) assertEquals(1, argCaptor.value.size) @@ -142,8 +154,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testRegisterReceiver_allUsers() { val spiedContext = spy(mContext) - val itemController = PrivacyItemController(spiedContext, callback) - + val itemController = PrivacyItemController(spiedContext) + itemController.setListening(true) verify(spiedContext, atLeastOnce()).registerReceiverAsUser( eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null), eq(null)) @@ -170,4 +182,54 @@ class PrivacyItemControllerTest : SysuiTestCase() { Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)) verify(userManager).getProfiles(anyInt()) } + + @Test + fun testAddMultipleCallbacks() { + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + testableLooper.processAllMessages() + verify(callback).privacyChanged(anyList()) + + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + verify(otherCallback).privacyChanged(anyList()) + // Adding a callback should not unnecessarily call previous ones + verifyNoMoreInteractions(callback) + } + + @Test + fun testMultipleCallbacksAreUpdated() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + testableLooper.processAllMessages() + verify(callback).privacyChanged(anyList()) + verify(otherCallback).privacyChanged(anyList()) + } + + @Test + fun testRemoveCallback() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) + privacyItemController.removeCallback(callback) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + testableLooper.processAllMessages() + verify(callback, never()).privacyChanged(anyList()) + verify(otherCallback).privacyChanged(anyList()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java index 2f6b22117a10..5ff9d1490f3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java @@ -28,7 +28,6 @@ import android.testing.TestableLooper; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; @@ -73,7 +72,8 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { @Test public void testExpanded() throws RemoteException { - mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true); + mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); waitForUiOffloadThread(); verify(mBarService, Mockito.never()).onNotificationExpansionChanged( @@ -82,7 +82,8 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { @Test public void testVisibleAndNotExpanded() throws RemoteException { - mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false); + mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); mLogger.onVisibilityChanged( Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), Collections.emptyList()); @@ -94,26 +95,33 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { @Test public void testVisibleAndExpanded() throws RemoteException { - mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true); + mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); mLogger.onVisibilityChanged( Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), Collections.emptyList()); waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN); + NOTIFICATION_KEY, true, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum()); } @Test public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException { - mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true); + mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); mLogger.onVisibilityChanged( - Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), + Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true, + NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)), Collections.emptyList()); waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); + NOTIFICATION_KEY, false, true, + // The last location seen should be logged (the one passed to onVisibilityChanged). + NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum() + ); } @Test @@ -121,11 +129,14 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { mLogger.onVisibilityChanged( Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), Collections.emptyList()); - mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true); + mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); + NOTIFICATION_KEY, false, true, + // The last location seen should be logged (the one passed to onExpansionChanged). + NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum()); } @Test @@ -133,15 +144,24 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { mLogger.onVisibilityChanged( Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), Collections.emptyList()); - mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true); - mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true); + mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); + mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); + NOTIFICATION_KEY, false, true, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum()); } private NotificationVisibility createNotificationVisibility(String key, boolean visibility) { - return NotificationVisibility.obtain(key, 0, 0, visibility); + return createNotificationVisibility(key, visibility, + NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); + } + + private NotificationVisibility createNotificationVisibility(String key, boolean visibility, + NotificationVisibility.NotificationLocation location) { + return NotificationVisibility.obtain(key, 0, 0, visibility, location); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 0899c73ab07b..fdc9e0c4d7e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -45,6 +45,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.Intent; import android.content.pm.PackageManager; +import android.metrics.LogMaker; import android.os.Binder; import android.os.Handler; import android.provider.Settings; @@ -55,6 +56,8 @@ import android.testing.TestableLooper; import android.util.ArraySet; import android.view.View; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.NotificationPresenter; @@ -92,6 +95,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private NotificationGutsManager mGutsManager; @Rule public MockitoRule mockito = MockitoJUnit.rule(); + @Mock private MetricsLogger mMetricsLogger; @Mock private NotificationPresenter mPresenter; @Mock private NotificationActivityStarter mNotificationActivityStarter; @Mock private NotificationStackScrollLayout mStackScroller; @@ -105,6 +109,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { Assert.sMainLooper = TestableLooper.get(this).getLooper(); mDependency.injectTestDependency(DeviceProvisionedController.class, mDeviceProvisionedController); + mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mHandler = Handler.createAsync(mTestableLooper.getLooper()); mHelper = new NotificationTestHelper(mContext); @@ -141,7 +146,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(row.getWindowToken()).thenReturn(new Binder()); when(row.getGuts()).thenReturn(guts); - mGutsManager.openGuts(row, 0, 0, menuItem); + assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem)); assertEquals(View.INVISIBLE, guts.getVisibility()); mTestableLooper.processAllMessages(); verify(guts).openControls( @@ -189,7 +194,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(entry.getRow()).thenReturn(row); when(entry.getGuts()).thenReturn(guts); - mGutsManager.openGuts(row, 0, 0, menuItem); + assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem)); mTestableLooper.processAllMessages(); verify(guts).openControls( eq(true), @@ -215,6 +220,34 @@ public class NotificationGutsManagerTest extends SysuiTestCase { } @Test + public void testOpenGutsLogging() { + NotificationGutsManager gutsManager = spy(mGutsManager); + doReturn(true).when(gutsManager).bindGuts(any(), any()); + + NotificationGuts guts = spy(new NotificationGuts(mContext)); + doReturn(true).when(guts).post(any()); + + ExpandableNotificationRow realRow = createTestNotificationRow(); + NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow); + + ExpandableNotificationRow row = spy(realRow); + when(row.getWindowToken()).thenReturn(new Binder()); + when(row.getGuts()).thenReturn(guts); + StatusBarNotification notification = spy(realRow.getStatusBarNotification()); + when(row.getStatusBarNotification()).thenReturn(notification); + + assertTrue(gutsManager.openGuts(row, 0, 0, menuItem)); + + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + verify(notification).getLogMaker(); + verify(mMetricsLogger).write(logMakerCaptor.capture()); + assertEquals(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS, + logMakerCaptor.getValue().getCategory()); + assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION, + logMakerCaptor.getValue().getType()); + } + + @Test public void testAppOpsSettingsIntent_camera() { ArraySet<Integer> ops = new ArraySet<>(); ops.add(OP_CAMERA); @@ -321,7 +354,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test @@ -349,16 +383,18 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test - public void testInitializeNotificationInfoView_importance() throws Exception { + public void testInitializeNotificationInfoView_highPriority() throws Exception { NotificationInfo notificationInfoView = mock(NotificationInfo.class); ExpandableNotificationRow row = spy(mHelper.createRow()); row.setBlockingHelperShowing(true); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; row.getEntry().importance = IMPORTANCE_DEFAULT; + row.getEntry().setIsHighPriority(true); when(row.getIsNonblockable()).thenReturn(false); StatusBarNotification statusBarNotification = row.getStatusBarNotification(); @@ -378,7 +414,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(IMPORTANCE_DEFAULT)); + eq(IMPORTANCE_DEFAULT), + eq(true) /* wasShownHighPriority */); } @Test @@ -407,7 +444,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test @@ -435,7 +473,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index f6791dd5778c..08955e3c4d5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -210,7 +210,7 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -223,7 +223,7 @@ public class NotificationInfoTest extends SysuiTestCase { .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -232,7 +232,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_noDelegate() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); @@ -251,7 +251,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Other")); @@ -263,7 +263,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider); @@ -280,7 +280,7 @@ public class NotificationInfoTest extends SysuiTestCase { .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -292,7 +292,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -301,7 +301,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false, IMPORTANCE_DEFAULT); + false, IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -314,7 +314,7 @@ public class NotificationInfoTest extends SysuiTestCase { eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false, IMPORTANCE_DEFAULT); + false, IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -323,7 +323,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -332,7 +332,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_BlockButton() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.int_block); final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize); assertEquals(VISIBLE, block.getVisibility()); @@ -343,7 +343,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_BlockButton_BlockHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT); + true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.block); final View interruptivenessSettings = mNotificationInfo.findViewById( R.id.interruptiveness_settings); @@ -356,7 +356,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); assertEquals(VISIBLE, silent.getVisibility()); assertEquals( @@ -368,7 +368,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); assertEquals(VISIBLE, silent.getVisibility()); assertEquals( @@ -381,7 +381,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, alert.getVisibility()); assertEquals( @@ -393,7 +393,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, alert.getVisibility()); assertEquals( @@ -405,7 +405,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, silent.getVisibility()); @@ -421,7 +421,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, silent.getVisibility()); @@ -437,7 +437,7 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.block); final View interruptivenessSettings = mNotificationInfo.findViewById( R.id.interruptiveness_settings); @@ -455,7 +455,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); - }, null, true, false, IMPORTANCE_DEFAULT); + }, null, true, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -467,7 +467,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -479,7 +479,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); - }, null, false, false, IMPORTANCE_DEFAULT); + }, null, false, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -488,11 +488,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { - }, null, true, false, IMPORTANCE_DEFAULT); + }, null, true, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -501,7 +501,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger).write(argThat(logMaker -> logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER @@ -513,7 +513,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true, - true, true, IMPORTANCE_DEFAULT); + true, true, IMPORTANCE_DEFAULT, true); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1)); } @@ -526,7 +526,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(null, c); latch.countDown(); - }, null, true, true, IMPORTANCE_DEFAULT); + }, null, true, true, IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.info).performClick(); // Verify that listener was triggered. @@ -539,7 +539,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT); + null, true, true, IMPORTANCE_DEFAULT, true); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -550,7 +550,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT); + null, true, true, IMPORTANCE_DEFAULT, true); final TextView blockView = mNotificationInfo.findViewById(R.id.block); assertEquals(GONE, blockView.getVisibility()); } @@ -559,7 +559,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_BlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false, - true, true, IMPORTANCE_DEFAULT); + true, true, IMPORTANCE_DEFAULT, true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText()); @@ -569,7 +569,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -580,7 +580,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -591,7 +591,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); mTestableLooper.processAllMessages(); @@ -605,7 +605,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); mTestableLooper.processAllMessages(); @@ -619,7 +619,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); mTestableLooper.processAllMessages(); @@ -633,7 +633,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); mTestableLooper.processAllMessages(); @@ -647,7 +647,7 @@ public class NotificationInfoTest extends SysuiTestCase { int originalImportance = mNotificationChannel.getImportance(); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -662,7 +662,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true, false); @@ -680,7 +680,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , - true, false /* isNonblockable */, IMPORTANCE_DEFAULT + true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -702,7 +702,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */, - true, false /* isNonblockable */, IMPORTANCE_DEFAULT + true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -724,7 +724,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -752,7 +752,7 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -781,7 +781,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */ , false /* isNonblockable */, true /* isForBlockingHelper */, true, true /* isUserSentimentNegative */, /* isNoisy */ - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true /* save */, false /* force */); @@ -800,7 +800,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -823,7 +823,7 @@ public class NotificationInfoTest extends SysuiTestCase { true /* isForBlockingHelper */, true, false /* isUserSentimentNegative */, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); NotificationGuts guts = mock(NotificationGuts.class); doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean()); mNotificationInfo.setGutsParent(guts); @@ -838,7 +838,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -852,7 +852,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -888,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase { false /* isNonblockable */, true /* isForBlockingHelper */, true /* isUserSentimentNegative */, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, + false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -913,7 +914,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -928,7 +929,7 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -949,7 +950,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.handleCloseControls(true, false); @@ -967,7 +968,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -988,7 +989,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1006,7 +1007,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1027,7 +1028,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1049,7 +1050,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1071,7 +1072,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1092,7 +1093,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1108,7 +1109,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1125,7 +1126,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { - }, null, null, true, true, IMPORTANCE_DEFAULT); + }, null, null, true, true, IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); mTestableLooper.processAllMessages(); @@ -1143,7 +1144,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { saveImportance.run(); - }, null, null, true, false, IMPORTANCE_DEFAULT + }, null, null, true, false, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -1170,7 +1171,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1183,7 +1184,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1196,7 +1197,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1210,7 +1211,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1224,7 +1225,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1236,7 +1237,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 736f3840b91a..ae70b01cd35c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -352,7 +352,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())).thenReturn(true); + when(row.getEntry().isHighPriority()).thenReturn(true); when(mStackScroller.getChildAt(i)).thenReturn(row); } @@ -368,8 +368,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())) - .thenReturn(false); + when(row.getEntry().isHighPriority()).thenReturn(false); when(mStackScroller.getChildAt(i)).thenReturn(row); } @@ -385,8 +384,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())) - .thenReturn(i < 3); + when(row.getEntry().isHighPriority()).thenReturn(i < 3); when(mStackScroller.getChildAt(i)).thenReturn(row); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index c0f7f0ce217f..1ded835e9651 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -85,7 +85,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_TWILIGHT); + ColorDisplayManager.AUTO_MODE_TWILIGHT); verify(mQsTileHost).addTile("night"); } @@ -95,7 +95,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_CUSTOM); + ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); verify(mQsTileHost).addTile("night"); } @@ -105,7 +105,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_DISABLED); + ColorDisplayManager.AUTO_MODE_DISABLED); verify(mQsTileHost, never()).addTile("night"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index b7b95ef3ff6a..3b98f0ca8ce2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -51,6 +51,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { private NotificationStackScrollLayout mNotificationStackScrollLayout; @Mock private KeyguardStatusView mKeyguardStatusView; + @Mock + private KeyguardStatusBarView mKeyguardStatusBar; private NotificationPanelView mNotificationPanelView; @Before @@ -93,6 +95,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { super(NotificationPanelViewTest.this.mContext, null); mNotificationStackScroller = mNotificationStackScrollLayout; mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView; + mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar; } } } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index efa4e79cc318..73fcb0150a9e 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6780,22 +6780,22 @@ message MetricsEvent { CONVERSATION_ACTIONS = 1615; // ACTION: Actions from a text classifier are shown to user. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN = 1616; - // ACTION: Event time of a text classifier event in unix timestamp. - // CATEGORY: CONVERSATION_ACTIONS, LANGUAGE_DETECTION + // FIELD: Event time of a text classifier event in unix timestamp. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q FIELD_TEXT_CLASSIFIER_EVENT_TIME = 1617; // ACTION: Users compose their own replies instead of using suggested ones. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_MANUAL_REPLY = 1618; // ACTION: Text classifier generates an action. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619; @@ -6840,7 +6840,7 @@ message MetricsEvent { // OPEN: Settings > Display > Adaptive sleep // OS: Q SETTINGS_ADAPTIVE_SLEEP = 1628; - + // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION. // The UI location of the notification containing the smart suggestions. // This is a NotificationLocation object (see the NotificationLocation @@ -6862,6 +6862,48 @@ message MetricsEvent { // OS: Q FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631; + // OPEN: Settings > System > Aware + // OS: Q + SETTINGS_AWARE = 1632; + + // OPEN: Settings > System > Aware > Disable > Dialog + // OS: Q + DIALOG_AWARE_DISABLE = 1633; + + // FIELD: Session ID of TextClassifierEvent. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SESSION_ID = 1634; + + // FIELD: First entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE = 1635; + // FIELD: Second entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE = 1636; + + // FIELD: Third entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE = 1637; + + // FIELD: Score of the suggestion. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SCORE = 1638; + + // FIELD: widget type, e.g: notification, textview + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_WIDGET_TYPE = 1639; + + // FIELD: version of the widget. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_WIDGET_VERSION = 1640; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 6ff2b35d49a0..3a8931630338 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -220,6 +220,12 @@ message SystemMessage { // Package: android NOTE_NETWORK_SUGGESTION_AVAILABLE = 51; + // Inform the user that the contaminant is detected on the USB port + NOTE_USB_CONTAMINANT_DETECTED = 52; + + // Inform that user that the USB port is free of contaminants. + NOTE_USB_CONTAMINANT_NOT_DETECTED = 53; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release @@ -235,6 +241,8 @@ message SystemMessage { NOTE_NETWORK_LOST_INTERNET = 742; // The system default network switched to a different network NOTE_NETWORK_SWITCH = 743; + // Device logged-in captive portal network successfully + NOTE_NETWORK_LOGGED_IN = 744; // Notify the user that their work profile has been deleted // Package: android diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 79b63bc55102..c063e82766ed 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -491,6 +491,9 @@ message WifiLog { // List of PNO scan stats, one element for each mobility state repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128; + + // Wifi p2p statistics + optional WifiP2pStats wifi_p2p_stats = 129; } // Information that gets logged for every WiFi connection. @@ -962,6 +965,10 @@ message StaEvent { // NetworkAgent Wifi usability score of connected wifi optional int32 last_wifi_usability_score = 15 [default = -1]; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 last_prediction_horizon_sec = 16 [default = -1]; } // Wi-Fi Aware metrics @@ -1679,6 +1686,10 @@ message WifiIsUnusableEvent { // NetworkAgent wifi usability score of connected wifi. // Defaults to -1 if the score was never set. optional int32 last_wifi_usability_score = 11 [default = -1]; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 last_prediction_horizon_sec = 12 [default = -1]; } message PasspointProfileTypeCount { @@ -1800,6 +1811,21 @@ message WifiUsabilityStatsEntry { // Sequence number from external system app to framework optional int32 seq_num_to_framework = 19; + + // The total time CCA is on busy status on the current frequency in ms + // counted from the last radio chip reset + optional int64 total_cca_busy_freq_time_ms = 20; + + // The total radio on time of the current frequency from the last radio + // chip reset + optional int64 total_radio_on_freq_time_ms = 21; + + // The total number of beacons received from the last radio chip reset + optional int64 total_beacon_rx = 22; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 prediction_horizon_sec = 23; } message WifiUsabilityStats { @@ -1849,3 +1875,135 @@ message DeviceMobilityStatePnoScanStats { // the total duration elapsed while in this mobility state with PNO scans running, in ms optional int64 pno_duration_ms = 4; } + +// The information about the Wifi P2p events. +message WifiP2pStats { + + // Group event list tracking sessions and client counts in tethered mode. + repeated GroupEvent group_event = 1; + + // Session information that gets logged for every Wifi P2p connection. + repeated P2pConnectionEvent connection_event = 2; + + // Number of persistent group in the user profile. + optional int32 num_persistent_group = 3; + + // Number of peer scan. + optional int32 num_total_peer_scans = 4; + + // Number of service scan. + optional int32 num_total_service_scans = 5; +} + +message P2pConnectionEvent { + + enum ConnectionType { + + // fresh new connection. + CONNECTION_FRESH = 0; + + // reinvoke a group. + CONNECTION_REINVOKE = 1; + + // create a group with the current device as the group owner locally. + CONNECTION_LOCAL = 2; + + // create a group or join a group with config. + CONNECTION_FAST = 3; + } + + enum ConnectivityLevelFailure { + + // Failure is unknown. + CLF_UNKNOWN = 0; + + // No failure. + CLF_NONE = 1; + + // Timeout for current connecting request. + CLF_TIMEOUT = 2; + + // The connecting request is canceled by the user. + CLF_CANCEL = 3; + + // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer. + CLF_PROV_DISC_FAIL = 4; + + // Invitation failure, e.g. rejected by the peer. + CLF_INVITATION_FAIL = 5; + + // Incoming request is rejected by the user. + CLF_USER_REJECT = 6; + + // New connection request is issued before ending previous connecting request. + CLF_NEW_CONNECTION_ATTEMPT = 7; + } + + // WPS method. + enum WpsMethod { + // WPS is skipped for Group Reinvoke. + WPS_NA = -1; + + // Push button configuration. + WPS_PBC = 0; + + // Display pin method configuration - pin is generated and displayed on device. + WPS_DISPLAY = 1; + + // Keypad pin method configuration - pin is entered on device. + WPS_KEYPAD = 2; + + // Label pin method configuration - pin is labelled on device. + WPS_LABEL = 3; + } + + // Start time of the connection. + optional int64 start_time_millis = 1; + + // Type of the connection. + optional ConnectionType connection_type = 2; + + // WPS method. + optional WpsMethod wps_method = 3 [default = WPS_NA]; + + // Duration to connect. + optional int32 duration_taken_to_connect_millis = 4; + + // Failures that happen at the connectivity layer. + optional ConnectivityLevelFailure connectivity_level_failure_code = 5; +} + +// GroupEvent tracking group information from GroupStarted to GroupRemoved. +message GroupEvent { + + enum GroupRole { + + GROUP_OWNER = 0; + + GROUP_CLIENT = 1; + } + + // The ID of network in supplicant for this group. + optional int32 net_id = 1; + + // Start time of the group. + optional int64 start_time_millis = 2; + + // Channel frequency used for Group. + optional int32 channel_frequency = 3; + + // Is group owner or group client. + optional GroupRole group_role = 5; + + // Number of connected clients. + optional int32 num_connected_clients = 6; + + // Cumulative number of connected clients. + optional int32 num_cumulative_clients = 7; + + // The session duration. + optional int32 session_duration_millis = 8; + + // The idle duration. A group without any client is in idle. + optional int32 idle_duration_millis = 9; +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index cf086812cb76..303230b00c6f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -17,13 +17,11 @@ package com.android.server.accessibility; import android.content.Context; -import android.os.Handler; import android.os.PowerManager; -import android.util.DebugUtils; -import android.util.ExceptionUtils; -import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.Display; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputFilter; @@ -31,10 +29,11 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.util.BitUtils; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; +import java.util.ArrayList; + /** * This class is an input filter for implementing accessibility features such * as display magnification and explore by touch. @@ -108,23 +107,24 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final AccessibilityManagerService mAms; - private boolean mInstalled; - - private int mUserId; - - private int mEnabledFeatures; + private final SparseArray<EventStreamTransformation> mEventHandler; - private TouchExplorer mTouchExplorer; + private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0); - private MagnificationGestureHandler mMagnificationGestureHandler; + private final SparseArray<MagnificationGestureHandler> mMagnificationGestureHandler = + new SparseArray<>(0); - private MotionEventInjector mMotionEventInjector; + private final SparseArray<MotionEventInjector> mMotionEventInjector = new SparseArray<>(0); private AutoclickController mAutoclickController; private KeyboardInterceptor mKeyboardInterceptor; - private EventStreamTransformation mEventHandler; + private boolean mInstalled; + + private int mUserId; + + private int mEnabledFeatures; private EventStreamState mMouseStreamState; @@ -133,10 +133,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private EventStreamState mKeyboardStreamState; AccessibilityInputFilter(Context context, AccessibilityManagerService service) { + this(context, service, new SparseArray<>(0)); + } + + AccessibilityInputFilter(Context context, AccessibilityManagerService service, + SparseArray<EventStreamTransformation> eventHandler) { super(context.getMainLooper()); mContext = context; mAms = service; mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mEventHandler = eventHandler; } @Override @@ -160,6 +166,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo super.onUninstalled(); } + void onDisplayChanged() { + if (mInstalled) { + disableFeatures(); + enableFeatures(); + } + } + @Override public void onInputEvent(InputEvent event, int policyFlags) { if (DEBUG) { @@ -167,8 +180,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo + Integer.toHexString(policyFlags)); } - if (mEventHandler == null) { - if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event); + if (mEventHandler.size() == 0) { + if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event); super.onInputEvent(event, policyFlags); return; } @@ -182,16 +195,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo int eventSource = event.getSource(); if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { state.reset(); - mEventHandler.clearEvents(eventSource); + clearEventsForAllEventHandlers(eventSource); super.onInputEvent(event, policyFlags); return; } - if (state.updateDeviceId(event.getDeviceId())) { - mEventHandler.clearEvents(eventSource); + if (state.updateInputSource(event.getSource())) { + clearEventsForAllEventHandlers(eventSource); } - if (!state.deviceIdValid()) { + if (!state.inputSourceValid()) { super.onInputEvent(event, policyFlags); return; } @@ -240,6 +253,15 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return null; } + private void clearEventsForAllEventHandlers(int eventSource) { + for (int i = 0; i < mEventHandler.size(); i++) { + final EventStreamTransformation eventHandler = mEventHandler.valueAt(i); + if (eventHandler != null) { + eventHandler.clearEvents(eventSource); + } + } + } + private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) { if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) { super.onInputEvent(event, policyFlags); @@ -258,7 +280,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo super.onInputEvent(event, policyFlags); return; } - mEventHandler.onKeyEvent(event, policyFlags); + // Since the display id of KeyEvent always would be -1 and there is only one + // KeyboardInterceptor for all display, pass KeyEvent to the mEventHandler of + // DEFAULT_DISPLAY to handle. + mEventHandler.get(Display.DEFAULT_DISPLAY).onKeyEvent(event, policyFlags); } private void handleMotionEvent(MotionEvent event, int policyFlags) { @@ -267,10 +292,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } mPm.userActivity(event.getEventTime(), false); MotionEvent transformedEvent = MotionEvent.obtain(event); - mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); + final int displayId = event.getDisplayId(); + mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY) + .onMotionEvent(transformedEvent, event, policyFlags); transformedEvent.recycle(); } + private boolean isDisplayIdValid(int displayId) { + return mEventHandler.get(displayId) != null; + } + @Override public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, int policyFlags) { @@ -323,14 +354,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } void notifyAccessibilityEvent(AccessibilityEvent event) { - if (mEventHandler != null) { - mEventHandler.onAccessibilityEvent(event); + for (int i = 0; i < mEventHandler.size(); i++) { + final EventStreamTransformation eventHandler = mEventHandler.valueAt(i); + if (eventHandler != null) { + eventHandler.onAccessibilityEvent(event); + } } } - void notifyAccessibilityButtonClicked() { - if (mMagnificationGestureHandler != null) { - mMagnificationGestureHandler.notifyShortcutTriggered(); + void notifyAccessibilityButtonClicked(int displayId) { + if (mMagnificationGestureHandler.size() != 0) { + final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId); + if (handler != null) { + handler.notifyShortcutTriggered(); + } } } @@ -339,81 +376,124 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo resetStreamState(); + final ArrayList<Display> displaysList = mAms.getValidDisplayList(); + if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { mAutoclickController = new AutoclickController(mContext, mUserId); - addFirstEventHandler(mAutoclickController); + addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController); } - if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { - mTouchExplorer = new TouchExplorer(mContext, mAms); - addFirstEventHandler(mTouchExplorer); - } + for (int i = displaysList.size() - 1; i >= 0; i--) { + final int displayId = displaysList.get(i).getDisplayId(); - if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { - final boolean detectControlGestures = (mEnabledFeatures - & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; - final boolean triggerable = (mEnabledFeatures - & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; - mMagnificationGestureHandler = new MagnificationGestureHandler( - mContext, mAms.getMagnificationController(), - detectControlGestures, triggerable); - addFirstEventHandler(mMagnificationGestureHandler); - } + if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + TouchExplorer explorer = new TouchExplorer(mContext, mAms); + addFirstEventHandler(displayId, explorer); + mTouchExplorer.put(displayId, explorer); + } - if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { - mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper()); - addFirstEventHandler(mMotionEventInjector); - mAms.setMotionEventInjector(mMotionEventInjector); + if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 + || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { + final boolean detectControlGestures = (mEnabledFeatures + & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; + final boolean triggerable = (mEnabledFeatures + & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; + MagnificationGestureHandler magnificationGestureHandler = + new MagnificationGestureHandler(mContext, + mAms.getMagnificationController(), + detectControlGestures, triggerable, displayId); + addFirstEventHandler(displayId, magnificationGestureHandler); + mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); + } + + if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { + MotionEventInjector injector = new MotionEventInjector( + mContext.getMainLooper()); + addFirstEventHandler(displayId, injector); + // TODO: Need to set MotionEventInjector per display. + mAms.setMotionEventInjector(injector); + mMotionEventInjector.put(displayId, injector); + } } if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { mKeyboardInterceptor = new KeyboardInterceptor(mAms, LocalServices.getService(WindowManagerPolicy.class)); - addFirstEventHandler(mKeyboardInterceptor); + // Since the display id of KeyEvent always would be -1 and it would be dispatched to + // the display with input focus directly, we only need one KeyboardInterceptor for + // default display. + addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor); } } /** - * Adds an event handler to the event handler chain. The handler is added at the beginning of - * the chain. + * Adds an event handler to the event handler chain for giving display. The handler is added at + * the beginning of the chain. * + * @param displayId The logical display id. * @param handler The handler to be added to the event handlers list. */ - private void addFirstEventHandler(EventStreamTransformation handler) { - if (mEventHandler != null) { - handler.setNext(mEventHandler); + private void addFirstEventHandler(int displayId, EventStreamTransformation handler) { + EventStreamTransformation eventHandler = mEventHandler.get(displayId); + if (eventHandler != null) { + handler.setNext(eventHandler); } else { handler.setNext(this); } - mEventHandler = handler; + eventHandler = handler; + mEventHandler.put(displayId, eventHandler); + } + + /** + * Adds an event handler to the event handler chain for all displays. The handler is added at + * the beginning of the chain. + * + * @param displayList The list of displays + * @param handler The handler to be added to the event handlers list. + */ + private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList, + EventStreamTransformation handler) { + for (int i = 0; i < displayList.size(); i++) { + final int displayId = displayList.get(i).getDisplayId(); + addFirstEventHandler(displayId, handler); + } } private void disableFeatures() { - if (mMotionEventInjector != null) { + for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) { + final MotionEventInjector injector = mMotionEventInjector.valueAt(i); + // TODO: Need to set MotionEventInjector per display. mAms.setMotionEventInjector(null); - mMotionEventInjector.onDestroy(); - mMotionEventInjector = null; + if (injector != null) { + injector.onDestroy(); + } } + mMotionEventInjector.clear(); if (mAutoclickController != null) { mAutoclickController.onDestroy(); mAutoclickController = null; } - if (mTouchExplorer != null) { - mTouchExplorer.onDestroy(); - mTouchExplorer = null; + for (int i = mTouchExplorer.size() - 1; i >= 0; i--) { + final TouchExplorer explorer = mTouchExplorer.valueAt(i); + if (explorer != null) { + explorer.onDestroy(); + } } - if (mMagnificationGestureHandler != null) { - mMagnificationGestureHandler.onDestroy(); - mMagnificationGestureHandler = null; + mTouchExplorer.clear(); + for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) { + final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i); + if (handler != null) { + handler.onDestroy(); + } } + mMagnificationGestureHandler.clear(); if (mKeyboardInterceptor != null) { mKeyboardInterceptor.onDestroy(); mKeyboardInterceptor = null; } - mEventHandler = null; + mEventHandler.clear(); resetStreamState(); } @@ -441,41 +521,41 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo * whose events should not be handled by a11y event stream transformations. */ private static class EventStreamState { - private int mDeviceId; + private int mSource; EventStreamState() { - mDeviceId = -1; + mSource = -1; } /** - * Updates the ID of the device associated with the state. If the ID changes, resets - * internal state. + * Updates the input source of the device associated with the state. If the source changes, + * resets internal state. * - * @param deviceId Updated input device ID. - * @return Whether the device ID has changed. + * @param source Updated input source. + * @return Whether the input source has changed. */ - public boolean updateDeviceId(int deviceId) { - if (mDeviceId == deviceId) { + public boolean updateInputSource(int source) { + if (mSource == source) { return false; } - // Reset clears internal state, so make sure it's called before |mDeviceId| is updated. + // Reset clears internal state, so make sure it's called before |mSource| is updated. reset(); - mDeviceId = deviceId; + mSource = source; return true; } /** - * @return Whether device ID is valid. + * @return Whether input source is valid. */ - public boolean deviceIdValid() { - return mDeviceId >= 0; + public boolean inputSourceValid() { + return mSource >= 0; } /** * Resets the event stream state. */ public void reset() { - mDeviceId = -1; + mSource = -1; } /** @@ -592,20 +672,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo /* * Key events from different devices may be interleaved. For example, the volume up and - * down keys can come from different device IDs. + * down keys can come from different input sources. */ @Override - public boolean updateDeviceId(int deviceId) { + public boolean updateInputSource(int deviceId) { return false; } - // We manage all device ids simultaneously; there is no concept of validity. + // We manage all input source simultaneously; there is no concept of validity. @Override - public boolean deviceIdValid() { + public boolean inputSourceValid() { return true; } - @Override final public boolean shouldProcessKeyEvent(KeyEvent event) { // For each keyboard device, wait for a down event from a device to start processing diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index dbe86c15c7a4..305c53e1e26a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -38,6 +38,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.PendingIntent; @@ -208,12 +209,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final WindowManagerInternal mWindowManagerService; - private final DisplayManager mDisplayManager; - private AppWidgetManagerInternal mAppWidgetService; private final SecurityPolicy mSecurityPolicy; + private final AccessibilityDisplayListener mA11yDisplayListener; + private final AppOpsManager mAppOpsManager; private final MainHandler mMainHandler; @@ -306,12 +307,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mMainHandler = new MainHandler(mContext.getMainLooper()); mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService); - mDisplayManager = mContext.getSystemService(DisplayManager.class); + mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( context.getContentResolver()); - registerDisplayListener(mMainHandler); } @Override @@ -527,30 +527,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }, UserHandle.ALL, intentFilter, null, null); } - private void registerDisplayListener(Handler handler) { - mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() { - @Override - public void onDisplayAdded(int displayId) { - synchronized (mLock) { - UserState userState = getCurrentUserStateLocked(); - updateMagnificationLocked(userState); - } - } - - @Override - public void onDisplayRemoved(int displayId) { - if (mMagnificationController != null) { - mMagnificationController.onDisplayRemoved(displayId); - } - } - - @Override - public void onDisplayChanged(int displayId) { - // do nothing - } - }, handler); - } - @Override public long addClient(IAccessibilityManagerClient callback, int userId) { synchronized (mLock) { @@ -937,16 +913,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Invoked remotely over AIDL by SysUi when the accessibility button within the system's * navigation area has been clicked. + * + * @param displayId The logical display id. */ @Override - public void notifyAccessibilityButtonClicked() { + public void notifyAccessibilityButtonClicked(int displayId) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " + android.Manifest.permission.STATUS_BAR_SERVICE); } synchronized (mLock) { - notifyAccessibilityButtonClickedLocked(); + notifyAccessibilityButtonClickedLocked(displayId); } } @@ -1249,7 +1227,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void notifyAccessibilityButtonClickedLocked() { + private void notifyAccessibilityButtonClickedLocked(int displayId) { final UserState state = getCurrentUserStateLocked(); int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0; @@ -1266,12 +1244,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (potentialTargets == 1) { if (state.mIsNavBarMagnificationEnabled) { mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this)); + AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this, + displayId)); return; } else { for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = state.mBoundServices.get(i); if (service.mRequestAccessibilityButton) { + // TODO(b/120762691): Need to notify each accessibility service if + // accessibility button is clicked per display. service.notifyAccessibilityButtonClickedLocked(); return; } @@ -1281,17 +1262,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (state.mServiceAssignedToAccessibilityButton == null && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) { mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::showAccessibilityButtonTargetSelection, this)); + AccessibilityManagerService::showAccessibilityButtonTargetSelection, this, + displayId)); } else if (state.mIsNavBarMagnificationEnabled && state.mIsNavBarMagnificationAssignedToAccessibilityButton) { mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this)); + AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this, + displayId)); return; } else { for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = state.mBoundServices.get(i); if (service.mRequestAccessibilityButton && (service.mComponentName.equals( state.mServiceAssignedToAccessibilityButton))) { + // TODO(b/120762691): Need to notify each accessibility service if + // accessibility button is clicked per display. service.notifyAccessibilityButtonClickedLocked(); return; } @@ -1299,22 +1284,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // The user may have turned off the assigned service or feature mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::showAccessibilityButtonTargetSelection, this)); + AccessibilityManagerService::showAccessibilityButtonTargetSelection, this, + displayId)); } } - private void sendAccessibilityButtonToInputFilter() { + private void sendAccessibilityButtonToInputFilter(int displayId) { synchronized (mLock) { if (mHasInputFilter && mInputFilter != null) { - mInputFilter.notifyAccessibilityButtonClicked(); + mInputFilter.notifyAccessibilityButtonClicked(displayId); } } } - private void showAccessibilityButtonTargetSelection() { + private void showAccessibilityButtonTargetSelection(int displayId) { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId)); + final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); + mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId)); } private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) { @@ -2226,32 +2213,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } - // register all display if global magnification is enabled. - final Display[] displays = mDisplayManager.getDisplays(); + // Get all valid displays and register them if global magnification is enabled. + // We would skip overlay display because it uses overlay window to simulate secondary + // displays in one display. It's not a real display and there's no input events for it. + final ArrayList<Display> displays = getValidDisplayList(); if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled) { - for (int i = 0; i < displays.length; i++) { - final Display display = displays[i]; - // Overlay display uses overlay window to simulate secondary displays in - // one display. It's not a real display and there's no input events for it. - // We should ignore it. - if (display.getType() == Display.TYPE_OVERLAY) { - continue; - } + for (int i = 0; i < displays.size(); i++) { + final Display display = displays.get(i); getMagnificationController().register(display.getDisplayId()); } return; } - // register if display has listening magnification services. - for (int i = 0; i < displays.length; i++) { - final Display display = displays[i]; - // Overlay display uses overlay window to simulate secondary displays in - // one display. It's not a real display and there's no input events for it. - // We should ignore it. - if (display.getType() == Display.TYPE_OVERLAY) { - continue; - } + // Register if display has listening magnification services. + for (int i = 0; i < displays.size(); i++) { + final Display display = displays.get(i); final int displayId = display.getDisplayId(); if (userHasListeningMagnificationServicesLocked(userState, displayId)) { getMagnificationController().register(displayId); @@ -3812,6 +3789,92 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + /** + * Gets all currently valid logical displays. + * + * @return An array list containing all valid logical displays. + */ + public ArrayList<Display> getValidDisplayList() { + return mA11yDisplayListener.getValidDisplayList(); + } + + /** + * A Utility class to handle display state. + */ + public class AccessibilityDisplayListener implements DisplayManager.DisplayListener { + private final DisplayManager mDisplayManager; + private final ArrayList<Display> mDisplaysList = new ArrayList<>(); + + AccessibilityDisplayListener(Context context, MainHandler handler) { + mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(this, handler); + initializeDisplayList(); + } + + ArrayList<Display> getValidDisplayList() { + synchronized (mLock) { + return mDisplaysList; + } + } + + private void initializeDisplayList() { + final Display[] displays = mDisplayManager.getDisplays(); + synchronized (mLock) { + mDisplaysList.clear(); + for (int i = 0; i < displays.length; i++) { + // Exclude overlay virtual displays. The display list is for A11yInputFilter + // to create event handler per display. The events should be handled by the + // display which is overlaid by it. + final Display display = displays[i]; + if (display.getType() == Display.TYPE_OVERLAY) { + continue; + } + mDisplaysList.add(display); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null || display.getType() == Display.TYPE_OVERLAY) { + return; + } + + synchronized (mLock) { + mDisplaysList.add(display); + if (mInputFilter != null) { + mInputFilter.onDisplayChanged(); + } + UserState userState = getCurrentUserStateLocked(); + updateMagnificationLocked(userState); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + for (int i = 0; i < mDisplaysList.size(); i++) { + if (mDisplaysList.get(i).getDisplayId() == displayId) { + mDisplaysList.remove(i); + break; + } + } + if (mInputFilter != null) { + mInputFilter.onDisplayChanged(); + } + } + if (mMagnificationController != null) { + mMagnificationController.onDisplayRemoved(displayId); + } + } + + @Override + public void onDisplayChanged(int displayId) { + /* do nothing */ + } + } + /** Represents an {@link AccessibilityManager} */ class Client { final IAccessibilityManagerClient mCallback; diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 49db488bc740..2fbaee65864a 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -43,7 +43,6 @@ import android.util.Log; import android.util.MathUtils; import android.util.Slog; import android.util.TypedValue; -import android.view.Display; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -149,6 +148,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; + private final int mDisplayId; + private final Queue<MotionEvent> mDebugInputEventHistory; private final Queue<MotionEvent> mDebugOutputEventHistory; @@ -162,11 +163,13 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some * external shortcut invoking {@link #notifyShortcutTriggered}, * {@code false} if it should ignore such triggers. + * @param displayId The logical display id. */ public MagnificationGestureHandler(Context context, MagnificationController magnificationController, boolean detectTripleTap, - boolean detectShortcutTrigger) { + boolean detectShortcutTrigger, + int displayId) { if (DEBUG_ALL) { Log.i(LOG_TAG, "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap @@ -174,6 +177,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } mMagnificationController = magnificationController; + mDisplayId = displayId; mDelegatingState = new DelegatingState(); mDetectingState = new DetectingState(context); @@ -259,8 +263,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { void notifyShortcutTriggered() { if (mDetectShortcutTrigger) { - // TODO: multi-display support for magnification gesture handler - boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY, + boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId, /* animate */ true); if (wasMagnifying) { clearAndTransitionToStateDetecting(); @@ -422,8 +425,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX + " scrollY: " + distanceY); } - // TODO: multi-display support for magnification gesture handler - mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX, + mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX, distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* event consumed: */ true; } @@ -440,8 +442,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return mScaling; } - // TODO: multi-display support for magnification gesture handler - final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY); + final float initialScale = mMagnificationController.getScale(mDisplayId); final float targetScale = initialScale * detector.getScaleFactor(); // Don't allow a gesture to move the user further outside the @@ -463,8 +464,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float pivotX = detector.getFocusX(); final float pivotY = detector.getFocusY(); if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x"); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false, + mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* handled: */ true; } @@ -524,10 +524,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } final float eventX = event.getX(); final float eventY = event.getY(); - // TODO: multi-display support for magnification gesture handler if (mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, eventX, eventY)) { - mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY, + mDisplayId, eventX, eventY)) { + mMagnificationController.setCenter(mDisplayId, eventX, eventY, /* animate */ mLastMoveOutsideMagnifiedRegion, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); mLastMoveOutsideMagnifiedRegion = false; @@ -665,9 +664,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { + mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -684,8 +682,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) - // TODO: multi-display support for magnification gesture handler - || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + || mMagnificationController.isMagnifying(mDisplayId)) { afterMultiTapTimeoutTransitionToDelegatingState(); @@ -697,8 +694,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } break; case ACTION_POINTER_DOWN: { - // TODO: multi-display support for magnification gesture handler - if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + if (mMagnificationController.isMagnifying(mDisplayId)) { transitionTo(mPanningScalingState); clear(); } else { @@ -727,9 +723,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); - // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { + mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -880,8 +875,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { clear(); // Toggle zoom - // TODO: multi-display support for magnification gesture handler - if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + if (mMagnificationController.isMagnifying(mDisplayId)) { zoomOff(); } else { zoomOn(up.getX(), up.getY()); @@ -893,9 +887,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()"); clear(); - // TODO: multi-display support for magnification gesture handler mViewportDraggingState.mZoomedInBeforeDrag = - mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY); + mMagnificationController.isMagnifying(mDisplayId); zoomOn(down.getX(), down.getY()); @@ -922,8 +915,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")"); mShortcutTriggered = state; - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state); + mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state); } /** @@ -958,8 +950,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float scale = MathUtils.constrain( mMagnificationController.getPersistedScale(), MIN_SCALE, MAX_SCALE); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY, + mMagnificationController.setScaleAndCenter(mDisplayId, scale, centerX, centerY, /* animate */ true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); @@ -967,8 +958,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private void zoomOff() { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()"); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true); + mMagnificationController.reset(mDisplayId, /* animate */ true); } private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) { @@ -990,6 +980,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) + ", mMagnificationController=" + mMagnificationController + + ", mDisplayId=" + mDisplayId + '}'; } diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index 8ffaddefd3ef..65e31f3acf14 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -435,7 +435,7 @@ class TouchExplorer extends BaseEventStreamTransformation MotionEvent click_event = MotionEvent.obtain(event.getDownTime(), event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, - event.getSource(), event.getFlags()); + event.getSource(), event.getDisplayId(), event.getFlags()); final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS); sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus); click_event.recycle(); @@ -1029,7 +1029,7 @@ class TouchExplorer extends BaseEventStreamTransformation event.getEventTime(), event.getAction(), event.getPointerCount(), props, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); + event.getSource(), event.getDisplayId(), event.getFlags()); } /** diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index c992da43fc07..2e45fa72eaac 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -18,14 +18,13 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTOFILL_MANAGER_SERVICE; -import static android.util.DebugUtils.flagsToString; import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS; +import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; import static com.android.server.autofill.Helper.sVerbose; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -61,6 +60,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillManager.SmartSuggestionMode; import android.view.autofill.AutofillManagerInternal; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManager; @@ -80,8 +80,6 @@ import com.android.server.infra.SecureSettingsServiceNameResolver; import java.io.FileDescriptor; 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.List; @@ -102,26 +100,6 @@ public final class AutofillManagerService private static final Object sLock = AutofillManagerService.class; - /** - * IME supports Smart Suggestions. - */ - // NOTE: must be public because of flagsToString() - public static final int FLAG_SMART_SUGGESTION_IME = 0x1; - - /** - * System supports Smarts Suggestions (as a popup-window similar to standard Autofill). - */ - // NOTE: must be public because of flagsToString() - public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x2; - - /** @hide */ - @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = { - FLAG_SMART_SUGGESTION_IME, - FLAG_SMART_SUGGESTION_SYSTEM - }) - @Retention(RetentionPolicy.SOURCE) - @interface SmartSuggestionMode {} - static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; private static final char COMPAT_PACKAGE_DELIMITER = ':'; @@ -484,7 +462,7 @@ public final class AutofillManagerService Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, 0); if (sDebug) { Slog.d(TAG, "setSmartSuggestionEmulationFromSettings(): " - + smartSuggestionFlagsToString(flags)); + + getSmartSuggestionModeToString(flags)); } synchronized (mLock) { @@ -698,10 +676,6 @@ public final class AutofillManagerService } } - static String smartSuggestionFlagsToString(int flags) { - return flagsToString(AutofillManagerService.class, "FLAG_SMART_SUGGESTION_", flags); - } - private final class LocalService extends AutofillManagerInternal { @Override public void onBackKeyPressed() { @@ -1251,7 +1225,7 @@ public final class AutofillManagerService pw.println(getWhitelistedCompatModePackagesFromSettings()); if (mSupportedSmartSuggestionModes != 0) { pw.print("Smart Suggestion modes: "); - pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes)); + pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes)); } if (showHistory) { pw.println(); pw.println("Requests history:"); pw.println(); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 954b67e4e2dc..8886ee2365c0 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -63,6 +63,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillManager.SmartSuggestionMode; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; @@ -72,7 +73,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; -import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode; import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.infra.AbstractPerUserSystemService; @@ -855,7 +855,6 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() { - // TODO(b/111330312): once we support IME, we need to set it per-user (OR'ed with master) return mMaster.getSupportedSmartSuggestionModesLocked(); } @@ -1049,7 +1048,7 @@ final class AutofillManagerServiceImpl componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() { @Override public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { - // TODO(b/111330312): properly implement + // TODO(b/123100811): properly implement Slog.w(TAG, "remote augmented autofill service died"); } }, mMaster.isInstantServiceAllowed(), mMaster.verbose); diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 5d8d8fa46d3f..9b863a9f2d26 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -113,7 +113,7 @@ final class RemoteAugmentedAutofillService scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest()); } - // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass + // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass private abstract static class MyPendingRequest extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> { protected final int mSessionId; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 9f7d33f6796a..194332ab8451 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -23,11 +23,10 @@ import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; +import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; +import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_IME; -import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_SYSTEM; -import static com.android.server.autofill.AutofillManagerService.smartSuggestionFlagsToString; import static com.android.server.autofill.Helper.getNumericValue; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; @@ -88,6 +87,7 @@ import android.util.TimeUtils; import android.view.KeyEvent; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillManager.SmartSuggestionMode; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; @@ -97,7 +97,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; -import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.PendingUi; @@ -250,7 +249,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Destroys the augmented Autofill UI. */ - // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the + // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the // main reason being the cases where user tap HOME. // Right now it's completely destroying the UI, but we need to decide whether / how to // properly recover it later (for example, if the user switches back to the activity, @@ -2559,7 +2558,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState notifyUnavailableToClient(AutofillManager.STATE_FINISHED); removeSelf(); } else { - // TODO(b/111330312, b/119638958): must set internal state so when user focus other + // TODO(b/123099468, b/119638958): must set internal state so when user focus other // fields it does not generate a new call to the standard autofill service (right now // it does). Must also add CTS tests to exercise this scenario. if (sVerbose) { @@ -2574,7 +2573,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @return callback to destroy the autofill UI, or {@code null} if not supported. */ - // TODO(b/111330312): might need to call it in other places, like when the service returns a + // TODO(b/123099468): might need to call it in other places, like when the service returns a // non-null response but without datasets (for example, just SaveInfo) @GuardedBy("mLock") private Runnable triggerAugmentedAutofillLocked() { @@ -2594,14 +2593,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Define which mode will be used final int mode; - if ((supportedModes & FLAG_SMART_SUGGESTION_IME) != 0) { - // TODO(b/111330312): support it :-) - Slog.w(TAG, "Smart Suggestions on IME not supported yet"); - return null; - } else if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) { + if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) { mode = FLAG_SMART_SUGGESTION_SYSTEM; + } else if ((supportedModes & AutofillManager.FLAG_SMART_SUGGESTION_LEGACY) != 0) { + mode = AutofillManager.FLAG_SMART_SUGGESTION_LEGACY; } else { - Slog.w(TAG, "Unsupported Smart Suggestion Mode: " + supportedModes); + Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes); return null; } @@ -2614,7 +2611,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "calling Augmented Autofill Service (" + remoteService.getComponentName().toShortString() + ") on view " + mCurrentViewId + " using suggestion mode " - + smartSuggestionFlagsToString(mode) + + getSmartSuggestionModeToString(mode) + " when server returned null for session " + this.id); } diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java index 4a1e5b9910bf..2241569afe18 100644 --- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java +++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java @@ -164,7 +164,7 @@ public class PackageManagerBackupAgent extends BackupAgent { int N = pkgs.size(); for (int a = N-1; a >= 0; a--) { PackageInfo pkg = pkgs.get(a); - if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) { + if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) { pkgs.remove(a); } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index b9a6f3c08cc4..303734a4043c 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static com.android.server.backup.BackupManagerService.TAG; +import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; @@ -41,9 +42,10 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.provider.Settings; +import android.os.UserManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import java.io.File; @@ -75,19 +77,25 @@ import java.io.PrintWriter; * system user is unlocked before any other users. */ public class Trampoline extends IBackupManager.Stub { - // When this file is present, the backup service is inactive. + /** + * Name of file that disables the backup service. If this file exists, then backup is disabled + * for all users. + */ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + /** + * Name of file for non-system users that enables the backup service for the user. Backup is + * disabled by default in non-system users. + */ + private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated"; + // Product-level suppression of backup/restore. private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; private static final String BACKUP_THREAD = "backup"; - /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */ - private static final int MULTI_USER_DISABLED = 0; - private static final int MULTI_USER_ENABLED = 1; - private final Context mContext; + private final UserManager mUserManager; private final boolean mGlobalDisable; // Lock to write backup suppress files. @@ -104,20 +112,13 @@ public class Trampoline extends IBackupManager.Stub { mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + mUserManager = UserManager.get(context); } protected boolean isBackupDisabled() { return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); } - private boolean isMultiUserEnabled() { - return Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.BACKUP_MULTI_USER_ENABLED, - MULTI_USER_DISABLED) - == MULTI_USER_ENABLED; - } - protected int binderGetCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } @@ -126,21 +127,65 @@ public class Trampoline extends IBackupManager.Stub { return Binder.getCallingUid(); } - protected File getSuppressFileForUser(int userId) { - return new File(UserBackupManagerFiles.getBaseStateDir(userId), + /** Stored in the system user's directory. */ + protected File getSuppressFileForSystemUser() { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), BACKUP_SUPPRESS_FILENAME); } - protected void createBackupSuppressFileForUser(int userId) throws IOException { - synchronized (mStateLock) { - getSuppressFileForUser(userId).getParentFile().mkdirs(); - getSuppressFileForUser(userId).createNewFile(); + /** Stored in the system user's directory and the file is indexed by the user it refers to. */ + protected File getActivatedFileForNonSystemUser(int userId) { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), + BACKUP_ACTIVATED_FILENAME + "-" + userId); + } + + private void createFile(File file) throws IOException { + if (file.exists()) { + return; + } + + file.getParentFile().mkdirs(); + if (!file.createNewFile()) { + Slog.w(TAG, "Failed to create file " + file.getPath()); + } + } + + private void deleteFile(File file) { + if (!file.exists()) { + return; + } + + if (!file.delete()) { + Slog.w(TAG, "Failed to delete file " + file.getPath()); } } - private void deleteBackupSuppressFileForUser(int userId) { - if (!getSuppressFileForUser(userId).delete()) { - Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId); + /** + * Deactivates the backup service for user {@code userId}. If this is the system user, it + * creates a suppress file which disables backup for all users. If this is a non-system user, it + * only deactivates backup for that user by deleting its activate file. + */ + @GuardedBy("mStateLock") + private void deactivateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + createFile(getSuppressFileForSystemUser()); + } else { + deleteFile(getActivatedFileForNonSystemUser(userId)); + } + } + + /** + * Enables the backup service for user {@code userId}. If this is the system user, it deletes + * the suppress file. If this is a non-system user, it creates the user's activate file. Note, + * deleting the suppress file does not automatically enable backup for non-system users, they + * need their own activate file in order to participate in the service. + */ + @GuardedBy("mStateLock") + private void activateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + deleteFile(getSuppressFileForSystemUser()); + } else { + createFile(getActivatedFileForNonSystemUser(userId)); } } @@ -148,24 +193,31 @@ public class Trampoline extends IBackupManager.Stub { // admin (device owner or profile owner). private boolean isUserReadyForBackup(int userId) { return mService != null && mService.getServiceUsers().get(userId) != null - && !isBackupSuppressedForUser(userId); + && isBackupActivatedForUser(userId); } - private boolean isBackupSuppressedForUser(int userId) { - // If backup is disabled for system user, it's disabled for all other users on device. - if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) { - return true; - } - if (userId != UserHandle.USER_SYSTEM) { - return getSuppressFileForUser(userId).exists(); + /** + * Backup is activated for the system user if the suppress file does not exist. Backup is + * activated for non-system users if the suppress file does not exist AND the user's activated + * file exists. + */ + private boolean isBackupActivatedForUser(int userId) { + if (getSuppressFileForSystemUser().exists()) { + return false; } - return false; + + return userId == UserHandle.USER_SYSTEM + || getActivatedFileForNonSystemUser(userId).exists(); } protected Context getContext() { return mContext; } + protected UserManager getUserManager() { + return mUserManager; + } + protected BackupManagerService createBackupManagerService() { return new BackupManagerService(mContext, this, mHandlerThread); } @@ -198,23 +250,17 @@ public class Trampoline extends IBackupManager.Stub { /** * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked. - * Starts the backup service for this user if it's the system user or if the service supports - * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time - * low. + * Starts the backup service for this user if backup is active for this user. Offloads work onto + * the handler thread {@link #mHandlerThread} to keep unlock time low. */ void unlockUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId); - return; - } - postToHandler(() -> startServiceForUser(userId)); } private void startServiceForUser(int userId) { // We know that the user is unlocked here because it is called from setBackupServiceActive // and unlockUser which have these guarantees. So we can check if the file exists. - if (mService != null && !isBackupSuppressedForUser(userId)) { + if (mService != null && isBackupActivatedForUser(userId)) { Slog.i(TAG, "Starting service for user: " + userId); mService.startServiceForUser(userId); } @@ -225,11 +271,6 @@ public class Trampoline extends IBackupManager.Stub { * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low. */ void stopUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId); - return; - } - postToHandler( () -> { if (mService != null) { @@ -240,45 +281,63 @@ public class Trampoline extends IBackupManager.Stub { } /** - * Only privileged callers should be changing the backup state. This method only acts on {@link - * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the - * system user also deactivates backup in all users. - * - * This call will only work if the calling {@code userID} is unlocked. + * The system user and managed profiles can only be acted on by callers in the system or root + * processes. Other users can be acted on by callers who have both android.permission.BACKUP and + * android.permission.INTERACT_ACROSS_USERS_FULL permissions. */ - public void setBackupServiceActive(int userId, boolean makeActive) { - int caller = binderGetCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { - throw new SecurityException("No permission to configure backup activity"); + private void enforcePermissionsOnUser(int userId) throws SecurityException { + boolean isRestrictedUser = + userId == UserHandle.USER_SYSTEM + || getUserManager().getUserInfo(userId).isManagedProfile(); + + if (isRestrictedUser) { + int caller = binderGetCallingUid(); + if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { + throw new SecurityException("No permission to configure backup activity"); + } + } else { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.BACKUP, "No permission to configure backup activity"); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "No permission to configure backup activity"); } + } + + /** + * Only privileged callers should be changing the backup state. Deactivating backup in the + * system user also deactivates backup in all users. We are not guaranteed that {@code userId} + * is unlocked at this point yet, so handle both cases. + */ + public void setBackupServiceActive(int userId, boolean makeActive) { + enforcePermissionsOnUser(userId); if (mGlobalDisable) { Slog.i(TAG, "Backup service not supported"); return; } - if (userId != UserHandle.USER_SYSTEM) { - Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId); - return; - } - - if (makeActive == isBackupServiceActive(userId)) { - Slog.i(TAG, "No change in backup service activity"); - return; - } - synchronized (mStateLock) { Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active"); if (makeActive) { if (mService == null) { mService = createBackupManagerService(); } - deleteBackupSuppressFileForUser(userId); - startServiceForUser(userId); + try { + activateBackupForUserLocked(userId); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity"); + } + + // If the user is unlocked, we can start the backup service for it. Otherwise we + // will start the service when the user is unlocked as part of its unlock callback. + if (getUserManager().isUserUnlocked(userId)) { + startServiceForUser(userId); + } } else { try { //TODO(b/121198006): what if this throws an exception? - createBackupSuppressFileForUser(userId); + deactivateBackupForUserLocked(userId); } catch (IOException e) { Slog.e(TAG, "Unable to persist backup service inactivity"); } @@ -636,14 +695,18 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void opComplete(int token, long result) throws RemoteException { - int userId = binderGetCallingUserId(); + public void opCompleteForUser(int userId, int token, long result) throws RemoteException { if (isUserReadyForBackup(userId)) { - mService.opComplete(binderGetCallingUserId(), token, result); + mService.opComplete(userId, token, result); } } @Override + public void opComplete(int token, long result) throws RemoteException { + opCompleteForUser(binderGetCallingUserId(), token, result); + } + + @Override public long getAvailableRestoreTokenForUser(int userId, String packageName) { return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId, packageName) : 0; diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 79f8a7e4e9ae..115e9240c6fe 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -804,7 +804,7 @@ public class UserBackupManagerService { public BackupAgent makeMetadataAgent() { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId); pmAgent.attach(mContext); - pmAgent.onCreate(); + pmAgent.onCreate(UserHandle.of(mUserId)); return pmAgent; } @@ -815,7 +815,7 @@ public class UserBackupManagerService { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, packages, mUserId); pmAgent.attach(mContext); - pmAgent.onCreate(); + pmAgent.onCreate(UserHandle.of(mUserId)); return pmAgent; } @@ -910,10 +910,10 @@ public class UserBackupManagerService { long lastBackup = in.readLong(); foundApps.add(pkgName); // all apps that we've addressed already try { - PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0); + PackageInfo pkg = mPackageManager.getPackageInfoAsUser(pkgName, 0, mUserId); if (AppBackupUtils.appGetsFullBackup(pkg) - && AppBackupUtils.appIsEligibleForBackup( - pkg.applicationInfo, mPackageManager)) { + && AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, + mUserId)) { schedule.add(new FullBackupEntry(pkgName, lastBackup)); } else { if (DEBUG) { @@ -933,8 +933,8 @@ public class UserBackupManagerService { // scan to make sure that we're tracking all full-backup candidates properly for (PackageInfo app : apps) { if (AppBackupUtils.appGetsFullBackup(app) - && AppBackupUtils.appIsEligibleForBackup( - app.applicationInfo, mPackageManager)) { + && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo, + mUserId)) { if (!foundApps.contains(app.packageName)) { if (MORE_DEBUG) { Slog.i(TAG, "New full backup app " + app.packageName + " found"); @@ -960,7 +960,7 @@ public class UserBackupManagerService { schedule = new ArrayList<>(apps.size()); for (PackageInfo info : apps) { if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup( - info.applicationInfo, mPackageManager)) { + info.applicationInfo, mUserId)) { schedule.add(new FullBackupEntry(info.packageName, 0)); } } @@ -1222,8 +1222,8 @@ public class UserBackupManagerService { mPackageManager.getPackageInfoAsUser( packageName, /* flags */ 0, mUserId); if (AppBackupUtils.appGetsFullBackup(app) - && AppBackupUtils.appIsEligibleForBackup( - app.applicationInfo, mPackageManager)) { + && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo, + mUserId)) { enqueueFullBackup(packageName, now); scheduleNextFullBackupJob(0); } else { @@ -1618,8 +1618,7 @@ public class UserBackupManagerService { try { PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); - if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, - mPackageManager)) { + if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) { BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; @@ -2095,7 +2094,8 @@ public class UserBackupManagerService { } try { - PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0); + PackageInfo appInfo = mPackageManager.getPackageInfoAsUser( + entry.packageName, 0, mUserId); if (!AppBackupUtils.appGetsFullBackup(appInfo)) { // The head app isn't supposed to get full-data backups [any more]; // so we cull it and force a loop around to consider the new head diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java index 31786d749f0a..0a7159bfe1b7 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java @@ -286,7 +286,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator(); while (iter.hasNext()) { PackageInfo pkg = iter.next().getValue(); - if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm) + if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, + mUserBackupManagerService.getUserId()) || AppBackupUtils.appIsStopped(pkg.applicationInfo)) { iter.remove(); if (DEBUG) { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index 0fb4f93e542b..86e679f16f66 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -143,6 +143,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private final int mBackupRunnerOpToken; private final OnTaskFinishedListener mListener; private final TransportClient mTransportClient; + private final int mUserId; // This is true when a backup operation for some package is in progress. private volatile boolean mIsDoingBackup; @@ -173,6 +174,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba mAgentTimeoutParameters = Preconditions.checkNotNull( backupManagerService.getAgentTimeoutParameters(), "Timeout parameters cannot be null"); + mUserId = backupManagerService.getUserId(); if (backupManagerService.isBackupOperationInProgress()) { if (DEBUG) { @@ -187,9 +189,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba for (String pkg : whichPackages) { try { PackageManager pm = backupManagerService.getPackageManager(); - PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES); + PackageInfo info = pm.getPackageInfoAsUser(pkg, + PackageManager.GET_SIGNING_CERTIFICATES, mUserId); mCurrentPackage = info; - if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) { + if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) { // Cull any packages that have indicated that backups are not permitted, // that run as system-domain uids but do not define their own backup agents, // as well as any explicit mention of the 'special' shared-storage agent @@ -633,7 +636,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba unregisterTask(); if (mJob != null) { - mJob.finishBackupPass(backupManagerService.getUserId()); + mJob.finishBackupPass(mUserId); } synchronized (backupManagerService.getQueueLock()) { diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index cfc129e11c6e..294eb0128b2c 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -489,7 +489,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { throw AgentException.permanent(e); } ApplicationInfo applicationInfo = packageInfo.applicationInfo; - if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) { + if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mUserId)) { mReporter.onPackageNotEligibleForBackup(packageName); throw AgentException.permanent(); } diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java index b3d9fbcb88d3..c5389fa5f878 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java @@ -230,7 +230,7 @@ public class FullRestoreEngine extends RestoreEngine { PackageManagerInternal.class); RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy( mBackupManagerService.getPackageManager(), allowApks, info, signatures, - pmi); + pmi, mUserId); mManifestSignatures.put(info.packageName, signatures); mPackagePolicies.put(pkg, restorePolicy); mPackageInstallers.put(pkg, info.installerPackageName); @@ -332,8 +332,9 @@ public class FullRestoreEngine extends RestoreEngine { } try { - mTargetApp = mBackupManagerService.getPackageManager() - .getApplicationInfoAsUser(pkg, 0, mUserId); + mTargetApp = + mBackupManagerService.getPackageManager() + .getApplicationInfoAsUser(pkg, 0, mUserId); // If we haven't sent any data to this app yet, we probably // need to clear it first. Check that. diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index d01f77bfd84c..7763d7b9adc0 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -232,7 +232,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { continue; } - if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) { + if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) { mAcceptSet.add(info); } } catch (NameNotFoundException e) { diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java index 054879b077ad..2db89289e4fc 100644 --- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java +++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java @@ -18,19 +18,25 @@ package com.android.server.backup.utils; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.TAG; +import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL; import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; import android.annotation.Nullable; +import android.app.AppGlobals; import android.app.backup.BackupTransport; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.SigningInfo; import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.internal.util.ArrayUtils; import com.android.server.backup.transport.TransportClient; @@ -39,7 +45,6 @@ import com.android.server.backup.transport.TransportClient; * Utility methods wrapping operations on ApplicationInfo and PackageInfo. */ public class AppBackupUtils { - private static final boolean DEBUG = false; /** @@ -54,15 +59,30 @@ public class AppBackupUtils { * <li>it is the special shared-storage backup package used for 'adb backup' * </ol> */ - public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) { + public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) { + return appIsEligibleForBackup(app, AppGlobals.getPackageManager(), userId); + } + + @VisibleForTesting + static boolean appIsEligibleForBackup(ApplicationInfo app, + IPackageManager packageManager, int userId) { // 1. their manifest states android:allowBackup="false" if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { return false; } - // 2. they run as a system-level uid but do not supply their own backup agent - if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) { - return false; + // 2. they run as a system-level uid + if ((app.uid < Process.FIRST_APPLICATION_UID)) { + // and the backup is happening for non-system user + if (userId != UserHandle.USER_SYSTEM && !app.packageName.equals( + PACKAGE_MANAGER_SENTINEL)) { + return false; + } + + // or do not supply their own backup agent + if (app.backupAgentName == null) { + return false; + } } // 3. it is the special shared-storage backup package used for 'adb backup' @@ -75,9 +95,7 @@ public class AppBackupUtils { return false; } - // Everything else checks out; the only remaining roadblock would be if the - // package were disabled - return !appIsDisabled(app, pm); + return !appIsDisabled(app, packageManager, userId); } /** @@ -99,9 +117,9 @@ public class AppBackupUtils { PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId); ApplicationInfo applicationInfo = packageInfo.applicationInfo; - if (!appIsEligibleForBackup(applicationInfo, pm) + if (!appIsEligibleForBackup(applicationInfo, userId) || appIsStopped(applicationInfo) - || appIsDisabled(applicationInfo, pm)) { + || appIsDisabled(applicationInfo, userId)) { return false; } if (transportClient != null) { @@ -123,8 +141,22 @@ public class AppBackupUtils { } /** Avoid backups of 'disabled' apps. */ - public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) { - switch (pm.getApplicationEnabledSetting(app.packageName)) { + static boolean appIsDisabled(ApplicationInfo app, int userId) { + return appIsDisabled(app, AppGlobals.getPackageManager(), userId); + } + + @VisibleForTesting + static boolean appIsDisabled(ApplicationInfo app, + IPackageManager packageManager, int userId) { + int enabledSetting; + try { + enabledSetting = packageManager.getApplicationEnabledSetting(app.packageName, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get application enabled setting: " + e); + return false; + } + + switch (enabledSetting) { case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java index 0f4b6810f15b..f4b235a3f3e1 100644 --- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java +++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java @@ -383,11 +383,12 @@ public class TarBackupReader { * @param allowApks - allow restore set to include apks. * @param info - file metadata. * @param signatures - array of signatures parsed from backup file. + * @param userId - ID of the user for which restore is performed. * @return a restore policy constant. */ public RestorePolicy chooseRestorePolicy(PackageManager packageManager, boolean allowApks, FileMetadata info, Signature[] signatures, - PackageManagerInternal pmi) { + PackageManagerInternal pmi, int userId) { if (signatures == null) { return RestorePolicy.IGNORE; } @@ -396,8 +397,8 @@ public class TarBackupReader { // Okay, got the manifest info we need... try { - PackageInfo pkgInfo = packageManager.getPackageInfo( - info.packageName, PackageManager.GET_SIGNING_CERTIFICATES); + PackageInfo pkgInfo = packageManager.getPackageInfoAsUser( + info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId); // Fall through to IGNORE if the app explicitly disallows backup final int flags = pkgInfo.applicationInfo.flags; if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index e4bbcd67d4df..844096d9d717 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -35,6 +35,7 @@ import android.os.UserManager; import android.util.LocalLog; import android.util.Slog; import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -224,6 +225,15 @@ public final class ContentCaptureManagerService extends } @Override + public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) { + Preconditions.checkNotNull(request); + synchronized (mLock) { + final ContentCapturePerUserService service = getServiceForUserLocked(userId); + service.removeUserDataLocked(request); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 8d2c79bd9923..bc0e19a69040 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -28,6 +28,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC import android.Manifest; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; @@ -35,18 +36,22 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; +import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; import com.android.server.infra.AbstractPerUserSystemService; @@ -249,6 +254,39 @@ final class ContentCapturePerUserService } @GuardedBy("mLock") + public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) { + if (!isEnabledLocked()) { + return; + } + assertCallerLocked(request.getPackageName()); + mRemoteService.onUserDataRemovalRequest(request); + } + + /** + * Asserts the component is owned by the caller. + */ + @GuardedBy("mLock") + private void assertCallerLocked(@NonNull String packageName) { + final PackageManager pm = getContext().getPackageManager(); + final int callingUid = Binder.getCallingUid(); + final int packageUid; + try { + packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); + } catch (NameNotFoundException e) { + throw new SecurityException("Could not verify UID for " + packageName); + } + if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) + .hasRunningActivity(callingUid, packageName)) { + final String[] packages = pm.getPackagesForUid(callingUid); + final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; + Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid + + ") passed package (" + packageName + ") owned by UID " + packageUid); + + throw new SecurityException("Invalid package: " + packageName); + } + } + + @GuardedBy("mLock") public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken, @NonNull Bundle data) { final String id = getSessionId(activityToken); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 12742ca0a46f..54eea5d8591c 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -26,6 +26,7 @@ import android.service.contentcapture.SnapshotData; import android.text.format.DateUtils; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; +import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; import com.android.internal.os.IResultReceiver; @@ -108,6 +109,13 @@ final class RemoteContentCaptureService scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData)); } + /** + * Called by {@link ContentCaptureServerSession} to request removal of user data. + */ + public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) { + scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request)); + } + public interface ContentCaptureServiceCallbacks extends VultureCallback<RemoteContentCaptureService> { // NOTE: so far we don't need to notify the callback implementation diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index fcd136c65169..e3dcb7d331cf 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -77,6 +77,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; +import android.util.LongArrayQueue; import android.util.NtpTrustedTime; import android.util.Pair; import android.util.Slog; @@ -91,6 +92,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocalLog; import com.android.internal.util.StatLogger; import com.android.server.AppStateTracker.Listener; @@ -145,6 +147,7 @@ class AlarmManagerService extends SystemService { static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; static final int TICK_HISTORY_DEPTH = 10; + static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000; // Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays static final int ACTIVE_INDEX = 0; @@ -195,6 +198,7 @@ class AlarmManagerService extends SystemService { ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>(); ArrayList<InFlight> mInFlight = new ArrayList<>(); AlarmHandler mHandler; + AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); Intent mTimeTickIntent; @@ -277,7 +281,91 @@ class AlarmManagerService extends SystemService { private AppStateTracker mAppStateTracker; private boolean mAppStandbyParole; - private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>(); + + /** + * A rolling window history of previous times when an alarm was sent to a package. + */ + private static class AppWakeupHistory { + private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory = + new ArrayMap<>(); + private long mWindowSize; + + AppWakeupHistory(long windowSize) { + mWindowSize = windowSize; + } + + void recordAlarmForPackage(String packageName, int userId, long nowElapsed) { + final Pair<String, Integer> packageUser = Pair.create(packageName, userId); + LongArrayQueue history = mPackageHistory.get(packageUser); + if (history == null) { + history = new LongArrayQueue(); + mPackageHistory.put(packageUser, history); + } + if (history.size() == 0 || history.peekLast() < nowElapsed) { + history.addLast(nowElapsed); + } + snapToWindow(history); + } + + void removeForUser(int userId) { + for (int i = mPackageHistory.size() - 1; i >= 0; i--) { + final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i); + if (packageUserKey.second == userId) { + mPackageHistory.removeAt(i); + } + } + } + + void removeForPackage(String packageName, int userId) { + final Pair<String, Integer> packageUser = Pair.create(packageName, userId); + mPackageHistory.remove(packageUser); + } + + private void snapToWindow(LongArrayQueue history) { + while (history.peekFirst() + mWindowSize < history.peekLast()) { + history.removeFirst(); + } + } + + int getTotalWakeupsInWindow(String packageName, int userId) { + final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId)); + return (history == null) ? 0 : history.size(); + } + + long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) { + final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId)); + if (history == null) { + return 0; + } + final int i = history.size() - positionFromEnd; + return (i < 0) ? 0 : history.get(i); + } + + void dump(PrintWriter pw, String prefix, long nowElapsed) { + dump(new IndentingPrintWriter(pw, " ").setIndent(prefix), nowElapsed); + } + + void dump(IndentingPrintWriter pw, long nowElapsed) { + pw.println("App Alarm history:"); + pw.increaseIndent(); + for (int i = 0; i < mPackageHistory.size(); i++) { + final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i); + final LongArrayQueue timestamps = mPackageHistory.valueAt(i); + pw.print(packageUser.first); + pw.print(", u"); + pw.print(packageUser.second); + pw.print(": "); + // limit dumping to a max of 100 values + final int lastIdx = Math.max(0, timestamps.size() - 100); + for (int j = timestamps.size() - 1; j >= lastIdx; j--) { + TimeUtils.formatDuration(timestamps.get(j), nowElapsed, pw); + pw.print(", "); + } + pw.println(); + } + pw.decreaseIndent(); + } + } /** * All times are in milliseconds. These constants are kept synchronized with the system @@ -302,6 +390,17 @@ class AlarmManagerService extends SystemService { = "allow_while_idle_whitelist_duration"; @VisibleForTesting static final String KEY_LISTENER_TIMEOUT = "listener_timeout"; + @VisibleForTesting + static final String KEY_APP_STANDBY_QUOTAS_ENABLED = "app_standby_quotas_enabled"; + private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window"; + @VisibleForTesting + final String[] KEYS_APP_STANDBY_QUOTAS = { + "standby_active_quota", + "standby_working_quota", + "standby_frequent_quota", + "standby_rare_quota", + "standby_never_quota", + }; // Keys for specifying throttling delay based on app standby bucketing private final String[] KEYS_APP_STANDBY_DELAY = { @@ -319,6 +418,18 @@ class AlarmManagerService extends SystemService { private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000; private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000; + private static final boolean DEFAULT_APP_STANDBY_QUOTAS_ENABLED = true; + private static final long DEFAULT_APP_STANDBY_WINDOW = 60 * 60 * 1000; // 1 hr + /** + * Max number of times an app can receive alarms in {@link #APP_STANDBY_WINDOW} + */ + private final int[] DEFAULT_APP_STANDBY_QUOTAS = { + 720, // Active + 10, // Working + 2, // Frequent + 1, // Rare + 0 // Never + }; private final long[] DEFAULT_APP_STANDBY_DELAYS = { 0, // Active 6 * 60_000, // Working @@ -348,8 +459,11 @@ class AlarmManagerService extends SystemService { // Direct alarm listener callback timeout public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT; + public boolean APP_STANDBY_QUOTAS_ENABLED = DEFAULT_APP_STANDBY_QUOTAS_ENABLED; + public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW; public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length]; + public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length]; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -409,48 +523,90 @@ class AlarmManagerService extends SystemService { DEFAULT_APP_STANDBY_DELAYS[ACTIVE_INDEX]); for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_DELAY.length; i++) { APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i], - Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i])); + Math.max(APP_STANDBY_MIN_DELAYS[i - 1], DEFAULT_APP_STANDBY_DELAYS[i])); + } + + APP_STANDBY_QUOTAS_ENABLED = mParser.getBoolean(KEY_APP_STANDBY_QUOTAS_ENABLED, + DEFAULT_APP_STANDBY_QUOTAS_ENABLED); + + APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW, + DEFAULT_APP_STANDBY_WINDOW); + if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) { + Slog.w(TAG, "Cannot exceed the app_standby_window size of " + + DEFAULT_APP_STANDBY_WINDOW); + APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW; + } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) { + // Not recommended outside of testing. + Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW); + } + + APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt( + KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX], + DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]); + for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) { + APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i], + Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i])); } updateAllowWhileIdleWhitelistDurationLocked(); } } - void dump(PrintWriter pw) { - pw.println(" Settings:"); + void dump(PrintWriter pw, String prefix) { + dump(new IndentingPrintWriter(pw, " ").setIndent(prefix)); + } + + void dump(IndentingPrintWriter pw) { + pw.println("Settings:"); - pw.print(" "); pw.print(KEY_MIN_FUTURITY); pw.print("="); + pw.increaseIndent(); + + pw.print(KEY_MIN_FUTURITY); pw.print("="); TimeUtils.formatDuration(MIN_FUTURITY, pw); pw.println(); - pw.print(" "); pw.print(KEY_MIN_INTERVAL); pw.print("="); + pw.print(KEY_MIN_INTERVAL); pw.print("="); TimeUtils.formatDuration(MIN_INTERVAL, pw); pw.println(); - pw.print(" "); pw.print(KEY_MAX_INTERVAL); pw.print("="); + pw.print(KEY_MAX_INTERVAL); pw.print("="); TimeUtils.formatDuration(MAX_INTERVAL, pw); pw.println(); - pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("="); + pw.print(KEY_LISTENER_TIMEOUT); pw.print("="); TimeUtils.formatDuration(LISTENER_TIMEOUT, pw); pw.println(); - pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("="); + pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw); pw.println(); - pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("="); + pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw); pw.println(); - pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("="); + pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) { - pw.print(" "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("="); + pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw); pw.println(); } + + pw.print(KEY_APP_STANDBY_QUOTAS_ENABLED); pw.print("="); + pw.println(APP_STANDBY_QUOTAS_ENABLED); + + pw.print(KEY_APP_STANDBY_WINDOW); pw.print("="); + TimeUtils.formatDuration(APP_STANDBY_WINDOW, pw); + pw.println(); + + for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) { + pw.print(KEYS_APP_STANDBY_QUOTAS[i]); pw.print("="); + pw.println(APP_STANDBY_QUOTAS[i]); + } + + pw.decreaseIndent(); } void dumpProto(ProtoOutputStream proto, long fieldId) { @@ -925,7 +1081,7 @@ class AlarmManagerService extends SystemService { if (targetPackages != null && !targetPackages.contains(packageUser)) { continue; } - if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) { + if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) { batch.remove(alarm); rescheduledAlarms.add(alarm); } @@ -1300,6 +1456,7 @@ class AlarmManagerService extends SystemService { synchronized (mLock) { mHandler = new AlarmHandler(); mConstants = new Constants(mHandler); + mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); mNextWakeup = mNextNonWakeup = 0; @@ -1583,6 +1740,27 @@ class AlarmManagerService extends SystemService { } /** + * Returns the maximum alarms that an app in the specified bucket can receive in a rolling time + * window given by {@link Constants#APP_STANDBY_WINDOW} + */ + @VisibleForTesting + int getQuotaForBucketLocked(int bucket) { + final int index; + if (bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE) { + index = ACTIVE_INDEX; + } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) { + index = WORKING_INDEX; + } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_FREQUENT) { + index = FREQUENT_INDEX; + } else if (bucket < UsageStatsManager.STANDBY_BUCKET_NEVER) { + index = RARE_INDEX; + } else { + index = NEVER_INDEX; + } + return mConstants.APP_STANDBY_QUOTAS[index]; + } + + /** * Return the minimum time that should elapse before an app in the specified bucket * can receive alarms again */ @@ -1608,7 +1786,7 @@ class AlarmManagerService extends SystemService { * @param alarm The alarm to adjust * @return true if the alarm delivery time was updated. */ - private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { + private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) { if (isExemptFromAppStandby(alarm)) { return false; } @@ -1629,18 +1807,49 @@ class AlarmManagerService extends SystemService { final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket( sourcePackage, sourceUserId, mInjector.getElapsedRealtime()); - final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId); - final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L); - if (lastElapsed > 0) { - final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket); - if (alarm.expectedWhenElapsed < minElapsed) { - alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; - } else { - // app is now eligible to run alarms at the originally requested window. + if (mConstants.APP_STANDBY_QUOTAS_ENABLED) { + // Quota deferring implementation: + final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage, + sourceUserId); + final int quotaForBucket = getQuotaForBucketLocked(standbyBucket); + boolean deferred = false; + if (wakeupsInWindow >= quotaForBucket) { + final long minElapsed; + if (quotaForBucket <= 0) { + // Just keep deferring for a day till the quota changes + minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY; + } else { + // Suppose the quota for window was q, and the qth last delivery time for this + // package was t(q) then the next delivery must be after t(q) + <window_size> + final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage, + sourceUserId, quotaForBucket); + minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW; + } + if (alarm.expectedWhenElapsed < minElapsed) { + alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; + deferred = true; + } + } + if (!deferred) { // Restore original requirements in case they were changed earlier. alarm.whenElapsed = alarm.expectedWhenElapsed; alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed; } + } else { + // Minimum delay deferring implementation: + final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage, + sourceUserId, 1); + if (lastElapsed > 0) { + final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket); + if (alarm.expectedWhenElapsed < minElapsed) { + alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; + } else { + // app is now eligible to run alarms at the originally requested window. + // Restore original requirements in case they were changed earlier. + alarm.whenElapsed = alarm.expectedWhenElapsed; + alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed; + } + } } return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed); } @@ -1696,7 +1905,7 @@ class AlarmManagerService extends SystemService { mAllowWhileIdleDispatches.add(ent); } } - adjustDeliveryTimeBasedOnStandbyBucketLocked(a); + adjustDeliveryTimeBasedOnBucketLocked(a); insertAndBatchAlarmLocked(a); if (a.alarmClock != null) { @@ -1915,7 +2124,7 @@ class AlarmManagerService extends SystemService { void dumpImpl(PrintWriter pw) { synchronized (mLock) { pw.println("Current Alarm Manager state:"); - mConstants.dump(pw); + mConstants.dump(pw, " "); pw.println(); if (mAppStateTracker != null) { @@ -2065,14 +2274,7 @@ class AlarmManagerService extends SystemService { pw.println(" none"); } - pw.println(" mLastAlarmDeliveredForPackage:"); - for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) { - Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i); - pw.print(" Package " + packageUser.first + ", User " + packageUser.second + ":"); - TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw); - pw.println(); - } - pw.println(); + mAppWakeupHistory.dump(pw, " ", nowELAPSED); if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) { pw.println(); @@ -3862,6 +4064,7 @@ class AlarmManagerService extends SystemService { obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget(); } + @Override public void handleMessage(Message msg) { switch (msg.what) { case ALARM_EVENT: { @@ -4030,64 +4233,57 @@ class AlarmManagerService extends SystemService { public void onReceive(Context context, Intent intent) { final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); synchronized (mLock) { - String action = intent.getAction(); String pkgList[] = null; - if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); - for (String packageName : pkgList) { - if (lookForPackageLocked(packageName)) { - setResultCode(Activity.RESULT_OK); - return; - } - } - return; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userHandle >= 0) { - removeUserLocked(userHandle); - for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { - final Pair<String, Integer> packageUser = - mLastAlarmDeliveredForPackage.keyAt(i); - if (packageUser.second == userHandle) { - mLastAlarmDeliveredForPackage.removeAt(i); + switch (intent.getAction()) { + case Intent.ACTION_QUERY_PACKAGE_RESTART: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + for (String packageName : pkgList) { + if (lookForPackageLocked(packageName)) { + setResultCode(Activity.RESULT_OK); + return; } } - } - } else if (Intent.ACTION_UID_REMOVED.equals(action)) { - if (uid >= 0) { - mLastAllowWhileIdleDispatch.delete(uid); - mUseAllowWhileIdleShortTime.delete(uid); - } - } else { - if (Intent.ACTION_PACKAGE_REMOVED.equals(action) - && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - // This package is being updated; don't kill its alarms. return; - } - Uri data = intent.getData(); - if (data != null) { - String pkg = data.getSchemeSpecificPart(); - if (pkg != null) { - pkgList = new String[]{pkg}; + case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + break; + case Intent.ACTION_USER_STOPPED: + final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + removeUserLocked(userHandle); + mAppWakeupHistory.removeForUser(userHandle); } - } + return; + case Intent.ACTION_UID_REMOVED: + if (uid >= 0) { + mLastAllowWhileIdleDispatch.delete(uid); + mUseAllowWhileIdleShortTime.delete(uid); + } + return; + case Intent.ACTION_PACKAGE_REMOVED: + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // This package is being updated; don't kill its alarms. + return; + } + // Intentional fall-through. + case Intent.ACTION_PACKAGE_RESTARTED: + final Uri data = intent.getData(); + if (data != null) { + final String pkg = data.getSchemeSpecificPart(); + if (pkg != null) { + pkgList = new String[]{pkg}; + } + } + break; } if (pkgList != null && (pkgList.length > 0)) { - for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { - Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i); - if (ArrayUtils.contains(pkgList, packageUser.first) - && packageUser.second == UserHandle.getUserId(uid)) { - mLastAlarmDeliveredForPackage.removeAt(i); - } - } for (String pkg : pkgList) { if (uid >= 0) { - // package-removed case + // package-removed and package-restarted case + mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid)); removeLocked(uid); } else { - // external-applications-unavailable etc case + // external-applications-unavailable case removeLocked(pkg); } mPriorities.remove(pkg); @@ -4131,7 +4327,8 @@ class AlarmManagerService extends SystemService { /** * Tracking of app assignments to standby buckets */ - final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener { + private final class AppStandbyTracker extends + UsageStatsManagerInternal.AppIdleStateChangeListener { @Override public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason) { @@ -4474,7 +4671,8 @@ class AlarmManagerService extends SystemService { if (!isExemptFromAppStandby(alarm)) { final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid)); - mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED); + mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage, + UserHandle.getUserId(alarm.creatorUid), nowELAPSED); } final BroadcastStats bs = inflight.mBroadcastStats; diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index a33338164caf..c4bc52c6f4de 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -208,6 +208,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private int mErrorRecoveryRetryCounter; private final int mSystemUiUid; + private boolean mIsHearingAidProfileSupported; + // Save a ProfileServiceConnections object for each of the bound // bluetooth profile services private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>(); @@ -391,13 +393,19 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); + mIsHearingAidProfileSupported = context.getResources() + .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported); + // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils - boolean isHearingAidEnabled; String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS); if (!TextUtils.isEmpty(value)) { - isHearingAidEnabled = Boolean.parseBoolean(value); + boolean isHearingAidEnabled = Boolean.parseBoolean(value); Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled); FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled); + if (isHearingAidEnabled && !mIsHearingAidProfileSupported) { + // Overwrite to enable support by FeatureFlag + mIsHearingAidProfileSupported = true; + } } IntentFilter filter = new IntentFilter(); @@ -679,6 +687,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } + @Override + public boolean isHearingAidProfileSupported() { + return mIsHearingAidProfileSupported; + } + // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fae7a8d7bfab..14e235489b97 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -73,6 +73,7 @@ import android.net.INetworkStatsService; import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; import android.net.MatchAllNetworkSpecifier; +import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; @@ -97,10 +98,10 @@ import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.shared.NetdService; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; -import android.net.util.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -237,6 +238,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // connect anyway?" dialog after the user selects a network that doesn't validate. private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; + // How long to dismiss network notification. + private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000; + // Default to 30s linger time-out. Modifiable only for testing. private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final int DEFAULT_LINGER_DELAY_MS = 30_000; @@ -473,6 +477,11 @@ public class ConnectivityService extends IConnectivityManager.Stub public static final int EVENT_PROVISIONING_NOTIFICATION = 43; /** + * This event can handle dismissing notification by given network id. + */ + public static final int EVENT_TIMEOUT_NOTIFICATION = 44; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -506,7 +515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell // the world when it changes. - private final ProxyTracker mProxyTracker; + @VisibleForTesting + protected final ProxyTracker mProxyTracker; final private SettingsObserver mSettingsObserver; @@ -815,7 +825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); - mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); + mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mKeyStore = KeyStore.getInstance(); @@ -981,6 +991,11 @@ public class ConnectivityService extends IConnectivityManager.Stub deps); } + @VisibleForTesting + protected ProxyTracker makeProxyTracker() { + return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); + } + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -2476,6 +2491,11 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; final boolean wasDefault = isDefaultNetwork(nai); + if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified + && valid) { + nai.captivePortalLoginNotified = true; + showNetworkNotification(nai, NotificationType.LOGGED_IN); + } final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; @@ -2496,7 +2516,15 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilities(oldScore, nai, nai.networkCapabilities); // If score has changed, rebroadcast to NetworkFactories. b/17726566 if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); - if (valid) handleFreshlyValidatedNetwork(nai); + if (valid) { + handleFreshlyValidatedNetwork(nai); + // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes + // valid. + mNotifier.clearNotification(nai.network.netId, + NotificationType.NO_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.LOST_INTERNET); + } } updateInetCondition(nai); // Let the NetworkAgent know the state of its network @@ -2520,6 +2548,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final int oldScore = nai.getCurrentScore(); nai.lastCaptivePortalDetected = visible; nai.everCaptivePortalDetected |= visible; + if (visible) { + nai.captivePortalLoginNotified = false; + } if (nai.lastCaptivePortalDetected && Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) { if (DBG) log("Avoiding captive portal network: " + nai.name()); @@ -2531,7 +2562,10 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilities(oldScore, nai, nai.networkCapabilities); } if (!visible) { - mNotifier.clearNotification(netId); + // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other + // notifications belong to the same network may be cleared unexpected. + mNotifier.clearNotification(netId, NotificationType.SIGN_IN); + mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH); } else { if (nai == null) { loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); @@ -2640,8 +2674,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private boolean networkRequiresValidation(NetworkAgentInfo nai) { - return isValidationRequired( - mDefaultRequest.networkCapabilities, nai.networkCapabilities); + return isValidationRequired(nai.networkCapabilities); } private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) { @@ -3238,9 +3271,15 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); } - private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) { + private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) { final String action; switch (type) { + case LOGGED_IN: + action = Settings.ACTION_WIFI_SETTINGS; + mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION); + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION, + nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS); + break; case NO_INTERNET: action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED; break; @@ -3253,10 +3292,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } Intent intent = new Intent(action); - intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setClassName("com.android.settings", - "com.android.settings.wifi.WifiNoInternetDialog"); + if (type != NotificationType.LOGGED_IN) { + intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName("com.android.settings", + "com.android.settings.wifi.WifiNoInternetDialog"); + } PendingIntent pendingIntent = PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); @@ -3274,7 +3315,7 @@ public class ConnectivityService extends IConnectivityManager.Stub !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { return; } - showValidationNotification(nai, NotificationType.NO_INTERNET); + showNetworkNotification(nai, NotificationType.NO_INTERNET); } private void handleNetworkUnvalidated(NetworkAgentInfo nai) { @@ -3283,7 +3324,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) { - showValidationNotification(nai, NotificationType.LOST_INTERNET); + showNetworkNotification(nai, NotificationType.LOST_INTERNET); } } @@ -3429,6 +3470,9 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_DATA_SAVER_CHANGED: handleRestrictBackgroundChanged(toBool(msg.arg1)); break; + case EVENT_TIMEOUT_NOTIFICATION: + mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN); + break; } } } @@ -3686,20 +3730,46 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Returns information about the proxy a certain network is using. If given a null network, it + * it will return the proxy for the bound network for the caller app or the default proxy if + * none. + * + * @param network the network we want to get the proxy information for. + * @return Proxy information if a network has a proxy configured, or otherwise null. + */ @Override public ProxyInfo getProxyForNetwork(Network network) { - if (network == null) return mProxyTracker.getDefaultProxy(); final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy(); if (globalProxy != null) return globalProxy; - if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null; - // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which - // caller may not have. + if (network == null) { + // Get the network associated with the calling UID. + final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(), + true); + if (activeNetwork == null) { + return null; + } + return getLinkPropertiesProxyInfo(activeNetwork); + } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) { + // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which + // caller may not have. + return getLinkPropertiesProxyInfo(network); + } + // No proxy info available if the calling UID does not have network access. + return null; + } + + @VisibleForTesting + protected boolean queryUserAccess(int uid, int netId) { + return NetworkUtils.queryUserAccess(uid, netId); + } + + private ProxyInfo getLinkPropertiesProxyInfo(Network network) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return null; synchronized (nai) { - final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy(); - if (proxyInfo == null) return null; - return new ProxyInfo(proxyInfo); + final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy(); + return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy); } } @@ -3723,11 +3793,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker.setDefaultProxy(proxy); } - // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy. - // This method gets called when any network changes proxy, but the broadcast only ever contains - // the default proxy (even if it hasn't changed). - // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network - // world where an app might be bound to a non-default network. + // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called + // when any network changes proxy. + // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a + // multi-network world where an app might be bound to a non-default network. private void updateProxy(LinkProperties newLp, LinkProperties oldLp) { ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy(); ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy(); @@ -5894,12 +5963,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } scheduleUnvalidatedPrompt(networkAgent); - if (networkAgent.isVPN()) { - // Temporarily disable the default proxy (not global). - mProxyTracker.setDefaultProxyEnabled(false); - // TODO: support proxy per network. - } - // Whether a particular NetworkRequest listen should cause signal strength thresholds to // be communicated to a particular NetworkAgent depends only on the network's immutable, // capabilities, so it only needs to be done once on initial connect, not every time the @@ -5918,10 +5981,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (state == NetworkInfo.State.DISCONNECTED) { networkAgent.asyncChannel.disconnect(); if (networkAgent.isVPN()) { - mProxyTracker.setDefaultProxyEnabled(true); updateUids(networkAgent, networkAgent.networkCapabilities, null); } disconnectAndDestroyNetwork(networkAgent); + if (networkAgent.isVPN()) { + // As the active or bound network changes for apps, broadcast the default proxy, as + // apps may need to update their proxy data. This is called after disconnecting from + // VPN to make sure we do not broadcast the old proxy data. + // TODO(b/122649188): send the broadcast only to VPN users. + mProxyTracker.sendProxyBroadcast(); + } } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) || state == NetworkInfo.State.SUSPENDED) { // going into or coming out of SUSPEND: re-score and notify @@ -6186,6 +6255,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId, + int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr, + String dstAddr) { + enforceKeepalivePermission(); + mKeepaliveTracker.startNattKeepalive( + getNetworkAgentInfoForNetwork(network), fd, resourceId, + intervalSeconds, messenger, binder, + srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT); + } + + @Override public void stopKeepalive(Network network, int slot) { mHandler.sendMessage(mHandler.obtainMessage( NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 121a830f05f5..39030aaf3eb4 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -341,6 +341,29 @@ public class DeviceIdleController extends SystemService @VisibleForTesting static final int STATE_QUICK_DOZE_DELAY = 7; + private static final int ACTIVE_REASON_UNKNOWN = 0; + private static final int ACTIVE_REASON_MOTION = 1; + private static final int ACTIVE_REASON_SCREEN = 2; + private static final int ACTIVE_REASON_CHARGING = 3; + private static final int ACTIVE_REASON_UNLOCKED = 4; + private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5; + private static final int ACTIVE_REASON_FORCED = 6; + private static final int ACTIVE_REASON_ALARM = 7; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_OK = 1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_INVALID = 3; + @VisibleForTesting + static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000; + @VisibleForTesting + static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f; + @VisibleForTesting static String stateToString(int state) { switch (state) { @@ -405,6 +428,7 @@ public class DeviceIdleController extends SystemService private long mNextSensingTimeoutAlarmTime; private long mCurIdleBudget; private long mMaintenanceStartTime; + private long mIdleStartTime; private int mActiveIdleOpCount; private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress @@ -415,6 +439,17 @@ public class DeviceIdleController extends SystemService private boolean mAlarmsActive; private boolean mReportedMaintenanceActivity; + /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter + * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because: + * - Both of them are shorter + * - Device sensor might take time be to become be stabilized + * Also don't apply the factor if the device is in motion because device motion provides a + * stronger signal than a prediction algorithm. + */ + private float mPreIdleFactor; + private float mLastPreIdleFactor; + private int mActiveReason; + public final AtomicFile mConfigFile; private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners = @@ -760,6 +795,10 @@ public class DeviceIdleController extends SystemService * exit doze. Default = true */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; + private static final String KEY_PRE_IDLE_FACTOR_LONG = + "pre_idle_factor_long"; + private static final String KEY_PRE_IDLE_FACTOR_SHORT = + "pre_idle_factor_short"; /** * This is the time, after becoming inactive, that we go in to the first @@ -987,6 +1026,16 @@ public class DeviceIdleController extends SystemService */ public long NOTIFICATION_WHITELIST_DURATION; + /** + * Pre idle time factor use to make idle delay longer + */ + public float PRE_IDLE_FACTOR_LONG; + + /** + * Pre idle time factor use to make idle delay shorter + */ + public float PRE_IDLE_FACTOR_SHORT; + public boolean WAIT_FOR_UNLOCK; private final ContentResolver mResolver; @@ -1082,6 +1131,8 @@ public class DeviceIdleController extends SystemService NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis( KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L); WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false); + PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f); + PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f); } } @@ -1196,6 +1247,12 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("="); pw.println(WAIT_FOR_UNLOCK); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("="); + pw.println(PRE_IDLE_FACTOR_LONG); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("="); + pw.println(PRE_IDLE_FACTOR_SHORT); } } @@ -1244,6 +1301,8 @@ public class DeviceIdleController extends SystemService private static final int MSG_FINISH_IDLE_OP = 8; private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9; private static final int MSG_SEND_CONSTRAINT_MONITORING = 10; + private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11; + private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12; final class MyHandler extends Handler { MyHandler(Looper looper) { @@ -1373,6 +1432,13 @@ public class DeviceIdleController extends SystemService constraint.stopMonitoring(); } } break; + case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + } break; + case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + maybeDoImmediateMaintenance(); + } break; } } } @@ -1526,6 +1592,28 @@ public class DeviceIdleController extends SystemService DeviceIdleController.this.unregisterMaintenanceActivityListener(listener); } + @Override public int setPreIdleTimeoutMode(int mode) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + return DeviceIdleController.this.setPreIdleTimeoutMode(mode); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void resetPreIdleTimeoutMode() { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + DeviceIdleController.this.resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { DeviceIdleController.this.dump(fd, pw, args); } @@ -1768,9 +1856,12 @@ public class DeviceIdleController extends SystemService // Start out assuming we are charging. If we aren't, we will at least get // a battery update the next time the level drops. mCharging = true; + mActiveReason = ACTIVE_REASON_UNKNOWN; mState = STATE_ACTIVE; mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; + mPreIdleFactor = 1.0f; + mLastPreIdleFactor = 1.0f; } mBinderService = new BinderService(); @@ -2394,6 +2485,7 @@ public class DeviceIdleController extends SystemService public void exitIdleInternal(String reason) { synchronized (this) { + mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL; becomeActiveLocked(reason, Binder.getCallingUid()); } } @@ -2463,6 +2555,7 @@ public class DeviceIdleController extends SystemService } else if (screenOn) { mScreenOn = true; if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) { + mActiveReason = ACTIVE_REASON_SCREEN; becomeActiveLocked("screen", Process.myUid()); } } @@ -2485,6 +2578,7 @@ public class DeviceIdleController extends SystemService } else if (charging) { mCharging = charging; if (!mForceIdle) { + mActiveReason = ACTIVE_REASON_CHARGING; becomeActiveLocked("charging", Process.myUid()); } } @@ -2516,6 +2610,7 @@ public class DeviceIdleController extends SystemService if (mScreenLocked != showing) { mScreenLocked = showing; if (mScreenOn && !mForceIdle && !mScreenLocked) { + mActiveReason = ACTIVE_REASON_UNLOCKED; becomeActiveLocked("unlocked", Process.myUid()); } } @@ -2587,7 +2682,11 @@ public class DeviceIdleController extends SystemService mState = STATE_INACTIVE; if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE"); resetIdleManagementLocked(); - scheduleAlarmLocked(mInactiveTimeout, false); + long delay = mInactiveTimeout; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); EventLogTags.writeDeviceIdle(mState, "no activity"); } } @@ -2605,6 +2704,7 @@ public class DeviceIdleController extends SystemService mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mNextLightIdleDelay = 0; + mIdleStartTime = 0; cancelAlarmLocked(); cancelSensingTimeoutAlarmLocked(); cancelLocatingLocked(); @@ -2621,6 +2721,7 @@ public class DeviceIdleController extends SystemService if (mForceIdle) { mForceIdle = false; if (mScreenOn || mCharging) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked("exit-force", Process.myUid()); } } @@ -2740,6 +2841,7 @@ public class DeviceIdleController extends SystemService if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) { // Whoops, there is an upcoming alarm. We don't actually want to go idle. if (mState != STATE_ACTIVE) { + mActiveReason = ACTIVE_REASON_ALARM; becomeActiveLocked("alarm", Process.myUid()); becomeInactiveIfAppropriateLocked(); } @@ -2763,7 +2865,11 @@ public class DeviceIdleController extends SystemService // We have now been inactive long enough, it is time to start looking // for motion and sleep some more while doing so. startMonitoringMotionLocked(); - scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); + long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); moveToStateLocked(STATE_IDLE_PENDING, reason); break; case STATE_IDLE_PENDING: @@ -2834,6 +2940,7 @@ public class DeviceIdleController extends SystemService " ms."); mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); + mIdleStartTime = SystemClock.elapsedRealtime(); mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT); if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { mNextIdleDelay = mConstants.IDLE_TIMEOUT; @@ -2934,6 +3041,127 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + int setPreIdleTimeoutMode(int mode) { + return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode)); + } + + @VisibleForTesting + float getPreIdleTimeoutByMode(int mode) { + switch (mode) { + case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: { + return mConstants.PRE_IDLE_FACTOR_LONG; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: { + return mConstants.PRE_IDLE_FACTOR_SHORT; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: { + return 1.0f; + } + default: { + Slog.w(TAG, "Invalid time out factor mode: " + mode); + return 1.0f; + } + } + } + + @VisibleForTesting + float getPreIdleTimeoutFactor() { + return mPreIdleFactor; + } + + @VisibleForTesting + int setPreIdleTimeoutFactor(float ratio) { + if (!mDeepEnabled) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable"); + return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT; + } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input"); + return SET_IDLE_FACTOR_RESULT_INVALID; + } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor"); + return SET_IDLE_FACTOR_RESULT_IGNORED; + } + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = ratio; + } + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio); + postUpdatePreIdleFactor(); + return SET_IDLE_FACTOR_RESULT_OK; + } + + @VisibleForTesting + void resetPreIdleTimeoutMode() { + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = 1.0f; + } + if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0"); + postResetPreIdleTimeoutFactor(); + } + + private void postUpdatePreIdleFactor() { + mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR); + } + + private void postResetPreIdleTimeoutFactor() { + mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR); + } + + @VisibleForTesting + void updatePreIdleFactor() { + synchronized (this) { + if (!shouldUseIdleTimeoutFactorLocked()) { + return; + } + if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) { + if (mNextAlarmTime == 0) { + return; + } + long delay = mNextAlarmTime - SystemClock.elapsedRealtime(); + if (delay < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor); + if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + scheduleAlarmLocked(newDelay, false); + } + } + } + + @VisibleForTesting + void maybeDoImmediateMaintenance() { + synchronized (this) { + if (mState == STATE_IDLE) { + long duration = SystemClock.elapsedRealtime() - mIdleStartTime; + /* Let's trgger a immediate maintenance, + * if it has been idle for a long time */ + if (duration > mConstants.IDLE_TIMEOUT) { + scheduleAlarmLocked(0, false); + } + } + } + } + + private boolean shouldUseIdleTimeoutFactorLocked() { + // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case + if (mActiveReason == ACTIVE_REASON_MOTION) { + return false; + } + return true; + } + + /** Must only be used in tests. */ + @VisibleForTesting + void setIdleStartTimeForTest(long idleStartTime) { + synchronized (this) { + mIdleStartTime = idleStartTime; + } + } + void reportMaintenanceActivityIfNeededLocked() { boolean active = mJobsActive; if (active == mReportedMaintenanceActivity) { @@ -2945,6 +3173,11 @@ public class DeviceIdleController extends SystemService mHandler.sendMessage(msg); } + @VisibleForTesting + long getNextAlarmTime() { + return mNextAlarmTime; + } + boolean isOpsInactiveLocked() { return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive; } @@ -2994,6 +3227,7 @@ public class DeviceIdleController extends SystemService scheduleReportActiveLocked(type, Process.myUid()); addEvent(EVENT_NORMAL, type); } + mActiveReason = ACTIVE_REASON_MOTION; mState = STATE_ACTIVE; mInactiveTimeout = timeout; mCurIdleBudget = 0; @@ -3401,6 +3635,11 @@ public class DeviceIdleController extends SystemService + "and any [-d] is ignored"); pw.println(" motion"); pw.println(" Simulate a motion event to bring the device out of deep doze"); + pw.println(" pre-idle-factor [0|1|2]"); + pw.println(" Set a new factor to idle time before step to idle" + + "(inactive_to and idle_after_inactive_to)"); + pw.println(" reset-pre-idle-factor"); + pw.println(" Reset factor to idle time to default"); } class Shell extends ShellCommand { @@ -3571,6 +3810,7 @@ public class DeviceIdleController extends SystemService } } if (becomeActive) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked((arg == null ? "all" : arg) + "-disabled", Process.myUid()); } @@ -3820,6 +4060,52 @@ public class DeviceIdleController extends SystemService Binder.restoreCallingIdentity(token); } } + } else if ("pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + int ret = SET_IDLE_FACTOR_RESULT_UNINIT; + try { + String arg = shell.getNextArg(); + boolean valid = false; + int mode = 0; + if (arg != null) { + mode = Integer.parseInt(arg); + ret = setPreIdleTimeoutMode(mode); + if (ret == SET_IDLE_FACTOR_RESULT_OK) { + pw.println("pre-idle-factor: " + mode); + valid = true; + } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) { + valid = true; + pw.println("Deep idle not supported"); + } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) { + valid = true; + pw.println("Idle timeout factor not changed"); + } + } + if (!valid) { + pw.println("Unknown idle timeout factor: " + arg + + ",(error code: " + ret + ")"); + } + } catch (NumberFormatException e) { + pw.println("Unknown idle timeout factor" + + ",(error code: " + ret + ")"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } else if ("reset-pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + try { + resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(token); + } + } } else { return shell.handleDefaultCommands(cmd); } @@ -4053,6 +4339,9 @@ public class DeviceIdleController extends SystemService if (mAlarmsActive) { pw.print(" mAlarmsActive="); pw.println(mAlarmsActive); } + if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) { + pw.print(" mPreIdleFactor="); pw.println(mPreIdleFactor); + } } } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 126bf6556538..371276fbd2ae 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -44,7 +44,7 @@ import android.net.LinkAddress; import android.net.Network; import android.net.NetworkUtils; import android.net.TrafficStats; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index b3a0f643ba4b..d2c6354e91b8 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -220,6 +220,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>(); + private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>(); + @GuardedBy("mLock") private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>(); @@ -353,6 +355,18 @@ public class LocationManagerService extends ILocationManager.Stub { } } }, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor( + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST), + true, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + synchronized (mLock) { + onIgnoreSettingsWhitelistChangedLocked(); + } + } + }, UserHandle.USER_ALL); new PackageMonitor() { @Override @@ -550,6 +564,25 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @GuardedBy("lock") + private void onIgnoreSettingsWhitelistChangedLocked() { + String setting = Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST); + if (setting == null) { + setting = ""; + } + + mIgnoreSettingsPackageWhitelist.clear(); + mIgnoreSettingsPackageWhitelist.addAll( + SystemConfig.getInstance().getAllowIgnoreLocationSettings()); + mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(","))); + + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); + } + } + @GuardedBy("mLock") private void onUserProfilesChangedLocked() { mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId); @@ -849,6 +882,15 @@ public class LocationManagerService extends ILocationManager.Stub { mAllowed = !mIsManagedBySettings; mEnabled = false; mProperties = null; + + if (mIsManagedBySettings) { + // since we assume providers are disabled by default + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + mName, + mCurrentUserId); + } } @GuardedBy("mLock") @@ -1290,8 +1332,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (provider == null) { continue; } - if (!provider.isUseableLocked() - && !updateRecord.mRealRequest.isLocationSettingsIgnored()) { + if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) { continue; } @@ -1979,7 +2020,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // requests that ignore location settings will never provider notifications - if (record.mRealRequest.isLocationSettingsIgnored()) { + if (isSettingsExemptLocked(record)) { continue; } @@ -2043,8 +2084,7 @@ public class LocationManagerService extends ILocationManager.Stub { record.mReceiver.mAllowedResolutionLevel)) { continue; } - if (!provider.isUseableLocked() - && !record.mRealRequest.isLocationSettingsIgnored()) { + if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { continue; } @@ -2154,6 +2194,25 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } + @GuardedBy("mLock") + private boolean isSettingsExemptLocked(UpdateRecord record) { + if (!record.mRealRequest.isLocationSettingsIgnored()) { + return false; + } + + if (mBackgroundThrottlePackageWhitelist.contains(record.mReceiver.mIdentity.mPackageName)) { + return true; + } + + for (LocationProvider provider : mProviders) { + if (record.mReceiver.mIdentity.mPackageName.equals(provider.getPackageLocked())) { + return true; + } + } + + return false; + } + private class UpdateRecord { final String mProvider; private final LocationRequest mRealRequest; // original request from client @@ -2297,7 +2356,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // make getFastestInterval() the minimum of interval and fastest interval if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) { - request.setFastestInterval(request.getInterval()); + sanitizedRequest.setFastestInterval(request.getInterval()); } return sanitizedRequest; } @@ -2409,7 +2468,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) { + if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { // Notify the listener that updates are currently disabled - but only if the request // does not ignore location settings receiver.callProviderEnabledLocked(name, false); @@ -2900,33 +2959,11 @@ public class LocationManagerService extends ILocationManager.Stub { long identity = Binder.clearCallingIdentity(); try { - boolean enabled; - try { - enabled = Settings.Secure.getIntForUser( + return Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.LOCATION_MODE, + Settings.Secure.LOCATION_MODE_OFF, userId) != Settings.Secure.LOCATION_MODE_OFF; - } catch (Settings.SettingNotFoundException e) { - // OS upgrade case where mode isn't set yet - enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - userId)); - - try { - Settings.Secure.putIntForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_MODE, - enabled - ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY - : Settings.Secure.LOCATION_MODE_OFF, - userId); - } catch (RuntimeException ex) { - // any problem with writing should not be propagated - Slog.e(TAG, "error updating location mode", ex); - } - } - return enabled; } finally { Binder.restoreCallingIdentity(identity); } @@ -3069,7 +3106,7 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) { + if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) { continue; } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index c84389bcee31..c064453360c5 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -60,7 +60,8 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; -import android.net.util.NetdService; +import android.net.shared.NetdService; +import android.net.shared.NetworkObserverRegistry; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -205,14 +206,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; + private NMSNetworkObserverRegistry mNetworkObserverRegistry; + private IBatteryStats mBatteryStats; private final Thread mThread; private CountDownLatch mConnectedSignal = new CountDownLatch(1); - private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = - new RemoteCallbackList<>(); - private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @GuardedBy("mTetheringStatsProviders") @@ -340,6 +340,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; + mNetworkObserverRegistry = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -387,14 +388,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void registerObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.register(observer); + mNetworkObserverRegistry.registerObserver(observer); } @Override public void unregisterObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.unregister(observer); + mNetworkObserverRegistry.unregisterObserver(observer); } @FunctionalInterface @@ -402,128 +401,101 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void sendCallback(INetworkManagementEventObserver o) throws RemoteException; } - private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { - final int length = mObservers.beginBroadcast(); - try { - for (int i = 0; i < length; i++) { - try { - eventCallback.sendCallback(mObservers.getBroadcastItem(i)); - } catch (RemoteException | RuntimeException e) { - } - } - } finally { - mObservers.finishBroadcast(); + private class NMSNetworkObserverRegistry extends NetworkObserverRegistry { + NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + super(context, handler, netd); } - } - - /** - * Notify our observers of an interface status change - */ - private void notifyInterfaceStatusChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); - } - /** - * Notify our observers of an interface link state change - * (typically, an Ethernet cable has been plugged-in or unplugged). - */ - private void notifyInterfaceLinkStateChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); - } - - /** - * Notify our observers of an interface addition. - */ - private void notifyInterfaceAdded(String iface) { - invokeForAllObservers(o -> o.interfaceAdded(iface)); - } - - /** - * Notify our observers of an interface removal. - */ - private void notifyInterfaceRemoved(String iface) { - // netd already clears out quota and alerts for removed ifaces; update - // our sanity-checking state. - mActiveAlerts.remove(iface); - mActiveQuotas.remove(iface); - - invokeForAllObservers(o -> o.interfaceRemoved(iface)); - } - - /** - * Notify our observers of a limit reached. - */ - private void notifyLimitReached(String limitName, String iface) { - invokeForAllObservers(o -> o.limitReached(limitName, iface)); - } - - /** - * Notify our observers of a change in the data activity state of the interface - */ - private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos, - int uid, boolean fromRadio) { - final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); - if (isMobile) { - if (!fromRadio) { - if (mMobileActivityFromRadio) { - // If this call is not coming from a report from the radio itself, but we - // have previously received reports from the radio, then we will take the - // power state to just be whatever the radio last reported. - powerState = mLastPowerStateFromRadio; + /** + * Notify our observers of a change in the data activity state of the interface + */ + @Override + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); + int powerState = isActive + ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + + if (isMobile) { + if (!fromRadio) { + if (mMobileActivityFromRadio) { + // If this call is not coming from a report from the radio itself, but we + // have previously received reports from the radio, then we will take the + // power state to just be whatever the radio last reported. + powerState = mLastPowerStateFromRadio; + } + } else { + mMobileActivityFromRadio = true; } - } else { - mMobileActivityFromRadio = true; - } - if (mLastPowerStateFromRadio != powerState) { - mLastPowerStateFromRadio = powerState; - try { - getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (mLastPowerStateFromRadio != powerState) { + mLastPowerStateFromRadio = powerState; + try { + getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } - } - if (ConnectivityManager.isNetworkTypeWifi(type)) { - if (mLastPowerStateFromWifi != powerState) { - mLastPowerStateFromWifi = powerState; - try { - getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } - } - - boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM - || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; - if (!isMobile || fromRadio || !mMobileActivityFromRadio) { - // Report the change in data activity. We don't do this if this is a change - // on the mobile network, that is not coming from the radio itself, and we - // have previously seen change reports from the radio. In that case only - // the radio is the authority for the current state. - final boolean active = isActive; - invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( - Integer.toString(type), active, tsNanos)); - } + if (!isMobile || fromRadio || !mMobileActivityFromRadio) { + // Report the change in data activity. We don't do this if this is a change + // on the mobile network, that is not coming from the radio itself, and we + // have previously seen change reports from the radio. In that case only + // the radio is the authority for the current state. + final boolean active = isActive; + super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio); + } - boolean report = false; - synchronized (mIdleTimerLock) { - if (mActiveIdleTimers.isEmpty()) { - // If there are no idle timers, we are not monitoring activity, so we - // are always considered active. - isActive = true; + boolean report = false; + synchronized (mIdleTimerLock) { + if (mActiveIdleTimers.isEmpty()) { + // If there are no idle timers, we are not monitoring activity, so we + // are always considered active. + isActive = true; + } + if (mNetworkActive != isActive) { + mNetworkActive = isActive; + report = isActive; + } } - if (mNetworkActive != isActive) { - mNetworkActive = isActive; - report = isActive; + if (report) { + reportNetworkActive(); } } - if (report) { - reportNetworkActive(); + + /** + * Notify our observers of an interface removal. + */ + @Override + public void notifyInterfaceRemoved(String iface) { + // netd already clears out quota and alerts for removed ifaces; update + // our sanity-checking state. + mActiveAlerts.remove(iface); + mActiveQuotas.remove(iface); + super.notifyInterfaceRemoved(iface); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); } } @@ -552,7 +524,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - notifyLimitReached(LIMIT_GLOBAL_ALERT, null); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached( + LIMIT_GLOBAL_ALERT, null)); } } @@ -583,6 +556,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); + try { + mNetworkObserverRegistry = new NMSNetworkObserverRegistry( + mContext, mDaemonHandler, mNetdService); + if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry"); + } catch (RemoteException | ServiceSpecificException e) { + Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e); + } } /** @@ -685,38 +665,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub } - /** - * Notify our observers of a new or updated interface address. - */ - private void notifyAddressUpdated(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressUpdated(iface, address)); - } - - /** - * Notify our observers of a deleted interface address. - */ - private void notifyAddressRemoved(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressRemoved(iface, address)); - } - - /** - * Notify our observers of DNS server information received. - */ - private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { - invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); - } - - /** - * Notify our observers of a route change. - */ - private void notifyRouteChange(String action, RouteInfo route) { - if (action.equals("updated")) { - invokeForAllObservers(o -> o.routeUpdated(route)); - } else { - invokeForAllObservers(o -> o.routeRemoved(route)); - } - } - // // Netd Callback handling // @@ -765,16 +713,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { - notifyInterfaceAdded(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { - notifyInterfaceRemoved(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { - notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceStatusChanged( + cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { - notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceLinkStateChanged( + cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); @@ -788,7 +738,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { - notifyLimitReached(cooked[3], cooked[4]); + mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); @@ -814,9 +764,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); - notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), - isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + mNetworkObserverRegistry.notifyInterfaceClassActivity( + Integer.parseInt(cooked[3]), isActive, timestampNanos, processUid, false); return true; // break; @@ -843,9 +792,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub } if (cooked[2].equals("updated")) { - notifyAddressUpdated(iface, address); + mNetworkObserverRegistry.notifyAddressUpdated(iface, address); } else { - notifyAddressRemoved(iface, address); + mNetworkObserverRegistry.notifyAddressRemoved(iface, address); } return true; // break; @@ -865,7 +814,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } String[] servers = cooked[5].split(","); - notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers); + mNetworkObserverRegistry.notifyInterfaceDnsServerInfo( + cooked[3], lifetime, servers); } return true; // break; @@ -904,7 +854,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - notifyRouteChange(cooked[2], route); + mNetworkObserverRegistry.notifyRouteChange( + cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1367,13 +1318,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(type, - DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1396,13 +1342,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(params.type, - DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, + false)); } } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 1e9a00743a8b..190fff1f669c 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -31,6 +31,19 @@ public interface PersistentDataBlockManagerInternal { */ byte[] getFrpCredentialHandle(); + /** Stores the data used to enable the Test Harness Mode after factory-resetting. */ + void setTestHarnessModeData(byte[] data); + + /** + * Retrieves the data used to place the device into Test Harness Mode. + * + * @throws IllegalStateException if the underlying storage is corrupt or inaccessible. + */ + byte[] getTestHarnessModeData(); + + /** Clear out the Test Harness Mode data. */ + void clearTestHarnessModeData(); + /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ void forceOemUnlockEnabled(boolean enabled); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 21093b9f6f88..bd5ad960a886 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -16,6 +16,8 @@ package com.android.server; +import static com.android.internal.util.Preconditions.checkArgument; + import android.Manifest; import android.app.ActivityManager; import android.content.Context; @@ -28,12 +30,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; -import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import libcore.io.IoUtils; @@ -65,6 +65,40 @@ import java.util.concurrent.TimeUnit; * * Clients can read any number of bytes from the currently written block up to its total size by * invoking {@link IPersistentDataBlockService#read} + * + * The persistent data block is currently laid out as follows: + * | ---------BEGINNING OF PARTITION-------------| + * | Partition digest (32 bytes) | + * | --------------------------------------------| + * | PARTITION_TYPE_MARKER (4 bytes) | + * | --------------------------------------------| + * | FRP data block length (4 bytes) | + * | --------------------------------------------| + * | FRP data (variable length) | + * | --------------------------------------------| + * | ... | + * | --------------------------------------------| + * | Test mode data block (10000 bytes) | + * | --------------------------------------------| + * | | Test mode data length (4 bytes) | + * | --------------------------------------------| + * | | Test mode data (variable length) | + * | | ... | + * | --------------------------------------------| + * | FRP credential handle block (1000 bytes) | + * | --------------------------------------------| + * | | FRP credential handle length (4 bytes)| + * | --------------------------------------------| + * | | FRP credential handle (variable len) | + * | | ... | + * | --------------------------------------------| + * | OEM Unlock bit (1 byte) | + * | ---------END OF PARTITION-------------------| + * + * TODO: now that the persistent partition contains several blocks, next time someone wants a new + * block, we should look at adding more generic block definitions and get rid of the various raw + * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain + * and less likely to introduce out-of-bounds read/write. */ public class PersistentDataBlockService extends SystemService { private static final String TAG = PersistentDataBlockService.class.getSimpleName(); @@ -73,10 +107,16 @@ public class PersistentDataBlockService extends SystemService { private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; - /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */ + /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; + /** + * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. + */ + private static final int TEST_MODE_RESERVED_SIZE = 10000; + /** Maximum size of the Test Harness Mode data that can be stored. */ + private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; // Limit to 100k as blocks larger than this might cause strain on Binder. private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100; @@ -221,6 +261,14 @@ public class PersistentDataBlockService extends SystemService { return mBlockDeviceSize; } + private long getFrpCredentialDataOffset() { + return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE; + } + + private long getTestHarnessModeDataOffset() { + return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE; + } + private boolean enforceChecksumValidity() { byte[] storedDigest = new byte[DIGEST_SIZE_BYTES]; @@ -383,7 +431,7 @@ public class PersistentDataBlockService extends SystemService { private long doGetMaximumDataBlockSize() { long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES - - FRP_CREDENTIAL_RESERVED_SIZE - 1; + - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } @@ -391,6 +439,13 @@ public class PersistentDataBlockService extends SystemService { private native int nativeWipe(String path); private final IBinder mService = new IPersistentDataBlockService.Stub() { + + /** + * Write the data to the persistent data block. + * + * @return a positive integer of the number of bytes that were written if successful, + * otherwise a negative integer indicating there was a problem + */ @Override public int write(byte[] data) throws RemoteException { enforceUid(Binder.getCallingUid()); @@ -597,12 +652,51 @@ public class PersistentDataBlockService extends SystemService { @Override public void setFrpCredentialHandle(byte[] handle) { - Preconditions.checkArgument(handle == null || handle.length > 0, - "handle must be null or non-empty"); - Preconditions.checkArgument(handle == null - || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE, - "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE); + writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + @Override + public byte[] getFrpCredentialHandle() { + return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + + @Override + public void setTestHarnessModeData(byte[] data) { + writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + } + + @Override + public byte[] getTestHarnessModeData() { + byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + if (data == null) { + return new byte[0]; + } + return data; + } + + @Override + public void clearTestHarnessModeData() { + int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4; + writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size)); + } + + private void writeInternal(byte[] data, long offset, int dataLength) { + checkArgument(data == null || data.length > 0, "data must be null or non-empty"); + checkArgument( + data == null || data.length <= dataLength, + "data must not be longer than " + dataLength); + + ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4); + dataBuffer.putInt(data == null ? 0 : data.length); + if (data != null) { + dataBuffer.put(data); + } + dataBuffer.flip(); + + writeDataBuffer(offset, dataBuffer); + } + + private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { FileOutputStream outputStream; try { outputStream = new FileOutputStream(new File(mDataBlockFile)); @@ -610,25 +704,15 @@ public class PersistentDataBlockService extends SystemService { Slog.e(TAG, "partition not available", e); return; } - - ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); - data.putInt(handle == null ? 0 : handle.length); - if (handle != null) { - data.put(handle); - } - data.flip(); - synchronized (mLock) { if (!mIsWritable) { IoUtils.closeQuietly(outputStream); return; } - try { FileChannel channel = outputStream.getChannel(); - - channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); - channel.write(data); + channel.position(offset); + channel.write(dataBuffer); outputStream.flush(); } catch (IOException e) { Slog.e(TAG, "unable to access persistent partition", e); @@ -641,8 +725,7 @@ public class PersistentDataBlockService extends SystemService { } } - @Override - public byte[] getFrpCredentialHandle() { + private byte[] readInternal(long offset, int maxLength) { if (!enforceChecksumValidity()) { throw new IllegalStateException("invalid checksum"); } @@ -652,14 +735,14 @@ public class PersistentDataBlockService extends SystemService { inputStream = new DataInputStream( new FileInputStream(new File(mDataBlockFile))); } catch (FileNotFoundException e) { - throw new IllegalStateException("frp partition not available"); + throw new IllegalStateException("persistent partition not available"); } try { synchronized (mLock) { - inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); + inputStream.skip(offset); int length = inputStream.readInt(); - if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) { + if (length <= 0 || length > maxLength) { return null; } byte[] bytes = new byte[length]; @@ -667,7 +750,7 @@ public class PersistentDataBlockService extends SystemService { return bytes; } } catch (IOException e) { - throw new IllegalStateException("frp handle not readable", e); + throw new IllegalStateException("persistent partition not readable", e); } finally { IoUtils.closeQuietly(inputStream); } diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index d09823efb6fa..a5a515f93e75 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -34,11 +34,11 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; @@ -48,6 +48,9 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; /** * Find the best Service, and bind to it. @@ -61,16 +64,20 @@ public class ServiceWatcher implements ServiceConnection { public static final String EXTRA_SERVICE_VERSION = "serviceVersion"; public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser"; + + /** Function to run on binder interface. */ + public interface BinderRunner { + /** Called to run client code with the binder. */ + void run(IBinder binder) throws RemoteException; + } + /** - * The runner that runs on the binder retrieved from {@link ServiceWatcher}. + * Function to run on binder interface. + * @param <T> Type to return. */ - public interface BinderRunner { - /** - * Runs on the retrieved binder. - * - * @param binder the binder retrieved from the {@link ServiceWatcher}. - */ - void run(IBinder binder); + public interface BlockingBinderRunner<T> { + /** Called to run client code with the binder. */ + T run(IBinder binder) throws RemoteException; } public static ArrayList<HashSet<Signature>> getSignatureSets(Context context, @@ -120,18 +127,14 @@ public class ServiceWatcher implements ServiceConnection { private final Handler mHandler; - // this lock is held to ensure the service binder is not exposed (via runOnBinder) until after - // the new service initialization work has completed - private final Object mBindLock = new Object(); - // read/write from handler thread + private IBinder mBestService; private int mCurrentUserId; // read from any thread, write from handler thread private volatile ComponentName mBestComponent; private volatile int mBestVersion; private volatile int mBestUserId; - private volatile IBinder mBestService; public ServiceWatcher(Context context, String logTag, String action, int overlaySwitchResId, int defaultServicePackageNameResId, @@ -163,17 +166,9 @@ public class ServiceWatcher implements ServiceConnection { mBestService = null; } - // called on handler thread - @GuardedBy("mBindLock") - protected void onBind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - } + protected void onBind() {} - // called on handler thread - @GuardedBy("mBindLock") - protected void onUnbind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - } + protected void onUnbind() {} /** * Start this watcher, including binding to the current best match and @@ -248,25 +243,6 @@ public class ServiceWatcher implements ServiceConnection { return bestComponent == null ? null : bestComponent.getPackageName(); } - /** - * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run - * serially. - */ - public final void runOnBinder(BinderRunner runner) { - synchronized (mBindLock) { - IBinder service = mBestService; - if (service != null) { - try { - runner.run(service); - } catch (Exception e) { - // remote exceptions cannot be allowed to crash system server - Log.e(TAG, "exception while while running " + runner + " on " + service - + " from " + this, e); - } - } - } - } - private boolean isServiceMissing() { return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction), PackageManager.MATCH_DIRECT_BOOT_AWARE @@ -380,28 +356,66 @@ public class ServiceWatcher implements ServiceConnection { mBestUserId = UserHandle.USER_NULL; } + /** + * Runs the given function asynchronously if currently connected. Suppresses any RemoteException + * thrown during execution. + */ + public final void runOnBinder(BinderRunner runner) { + runOnHandler(() -> { + if (mBestService == null) { + return; + } + + try { + runner.run(mBestService); + } catch (RuntimeException e) { + // the code being run is privileged, but may be outside the system server, and thus + // we cannot allow runtime exceptions to crash the system server + Log.e(TAG, "exception while while running " + runner + " on " + mBestService + + " from " + this, e); + } catch (RemoteException e) { + // do nothing + } + }); + } + + /** + * Runs the given function synchronously if currently connected, and returns the default value + * if not currently connected or if any exception is thrown. + */ + public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) { + try { + return runOnHandlerBlocking(() -> { + if (mBestService == null) { + return defaultValue; + } + + try { + return runner.run(mBestService); + } catch (RemoteException e) { + return defaultValue; + } + }); + } catch (InterruptedException e) { + return defaultValue; + } + } + @Override public final void onServiceConnected(ComponentName component, IBinder binder) { - mHandler.post(() -> { + runOnHandler(() -> { if (D) Log.d(mTag, component + " connected"); - - // hold the lock so that mBestService cannot be used by runOnBinder until complete - synchronized (mBindLock) { - mBestService = binder; - onBind(); - } + mBestService = binder; + onBind(); }); } @Override public final void onServiceDisconnected(ComponentName component) { - mHandler.post(() -> { + runOnHandler(() -> { if (D) Log.d(mTag, component + " disconnected"); - mBestService = null; - synchronized (mBindLock) { - onUnbind(); - } + onUnbind(); }); } @@ -410,4 +424,32 @@ public class ServiceWatcher implements ServiceConnection { ComponentName bestComponent = mBestComponent; return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion; } + + private void runOnHandler(Runnable r) { + if (Looper.myLooper() == mHandler.getLooper()) { + r.run(); + } else { + mHandler.post(r); + } + } + + private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException { + if (Looper.myLooper() == mHandler.getLooper()) { + try { + return c.call(); + } catch (Exception e) { + // Function cannot throw exception, this should never happen + throw new IllegalStateException(e); + } + } else { + FutureTask<T> task = new FutureTask<>(c); + mHandler.post(task); + try { + return task.get(); + } catch (ExecutionException e) { + // Function cannot throw exception, this should never happen + throw new IllegalStateException(e); + } + } + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9d810cd8f3ca..cec825fb9c00 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -112,6 +112,7 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.sysprop.VoldProperties; @@ -463,6 +464,7 @@ class StorageManagerService extends IStorageManager.Stub = { "password", "default", "pattern", "pin" }; private final Context mContext; + private final ContentResolver mResolver; private volatile IVold mVold; private volatile IStoraged mStoraged; @@ -797,6 +799,14 @@ class StorageManagerService extends IStorageManager.Stub refreshIsolatedStorageSettings(); } }); + // For now, simply clone property when it changes + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE, + mContext.getMainExecutor(), (namespace, name, value) -> { + if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) { + Settings.Global.putString(mResolver, + Settings.Global.ISOLATED_STORAGE_REMOTE, value); + } + }); refreshIsolatedStorageSettings(); } @@ -1523,6 +1533,8 @@ class StorageManagerService extends IStorageManager.Stub SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false))); mContext = context; + mResolver = mContext.getContentResolver(); + mCallbacks = new Callbacks(FgThread.get().getLooper()); mLockPatternUtils = new LockPatternUtils(mContext); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 16b12f1f1d68..1870f8d95977 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -2,6 +2,7 @@ "presubmit": [ { "name": "FrameworksMockingServicesTests", + "file_patterns": ["AlarmManagerService\\.java"], "options": [ { "include-annotation": "android.platform.test.annotations.Presubmit" diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index aa2389056c13..e5436178217d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2115,6 +2115,16 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + + if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + return true; } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index e879efd644d1..c826df06c925 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5202,7 +5202,7 @@ public class AccountManagerService Process.SYSTEM_UID, null /* packageName */, false); fout.println("Accounts: " + accounts.length); for (Account account : accounts) { - fout.println(" " + account.toSafeString()); + fout.println(" " + account.toString()); } // Add debug information. diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index ccead6c55b8c..c7b9a3cb7847 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -18,6 +18,7 @@ package com.android.server.adb; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; +import android.annotation.TestApi; import android.app.ActivityManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -26,6 +27,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.debug.AdbProtoEnums; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Environment; @@ -37,21 +39,36 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.adb.AdbDebuggingManagerProto; +import android.util.AtomicFile; import android.util.Base64; import android.util.Slog; +import android.util.StatsLog; +import android.util.Xml; import com.android.internal.R; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; /** * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi @@ -63,6 +80,7 @@ public class AdbDebuggingManager { private static final String ADBD_SOCKET = "adbd"; private static final String ADB_DIRECTORY = "misc/adb"; + // This file contains keys that will always be allowed to connect to the device via adb. private static final String ADB_KEYS_FILE = "adb_keys"; private static final int BUFFER_SIZE = 4096; @@ -71,12 +89,25 @@ public class AdbDebuggingManager { private AdbDebuggingThread mThread; private boolean mAdbEnabled = false; private String mFingerprints; + private String mConnectedKey; + private String mConfirmComponent; public AdbDebuggingManager(Context context) { mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); mContext = context; } + /** + * Constructor that accepts the component to be invoked to confirm if the user wants to allow + * an adb connection from the key. + */ + @TestApi + protected AdbDebuggingManager(Context context, String confirmComponent) { + mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); + mContext = context; + mConfirmComponent = confirmComponent; + } + class AdbDebuggingThread extends Thread { private boolean mStopped; private LocalSocket mSocket; @@ -135,7 +166,9 @@ public class AdbDebuggingManager { byte[] buffer = new byte[BUFFER_SIZE]; while (true) { int count = mInputStream.read(buffer); - if (count < 0) { + // if less than 2 bytes are read the if statements below will throw an + // IndexOutOfBoundsException. + if (count < 2) { break; } @@ -146,6 +179,11 @@ public class AdbDebuggingManager { AdbDebuggingHandler.MESSAGE_ADB_CONFIRM); msg.obj = key; mHandler.sendMessage(msg); + } else if (buffer[0] == 'D' && buffer[1] == 'C') { + Slog.d(TAG, "Received disconnected message"); + Message msg = mHandler.obtainMessage( + AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT); + mHandler.sendMessage(msg); } else { Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2)))); @@ -202,15 +240,41 @@ public class AdbDebuggingManager { } class AdbDebuggingHandler extends Handler { - private static final int MESSAGE_ADB_ENABLED = 1; - private static final int MESSAGE_ADB_DISABLED = 2; - private static final int MESSAGE_ADB_ALLOW = 3; - private static final int MESSAGE_ADB_DENY = 4; - private static final int MESSAGE_ADB_CONFIRM = 5; - private static final int MESSAGE_ADB_CLEAR = 6; + // The time to schedule the job to keep the key store updated with a currently connected + // key. This job is required because a deveoper could keep a device connected to their + // system beyond the time within which a subsequent connection is allowed. But since the + // last connection time is only written when a device is connected and disconnected then + // if the device is rebooted while connected to the development system it would appear as + // though the adb grant for the system is no longer authorized and the developer would need + // to manually allow the connection again. + private static final long UPDATE_KEY_STORE_JOB_INTERVAL = 86400000; + + static final int MESSAGE_ADB_ENABLED = 1; + static final int MESSAGE_ADB_DISABLED = 2; + static final int MESSAGE_ADB_ALLOW = 3; + static final int MESSAGE_ADB_DENY = 4; + static final int MESSAGE_ADB_CONFIRM = 5; + static final int MESSAGE_ADB_CLEAR = 6; + static final int MESSAGE_ADB_DISCONNECT = 7; + static final int MESSAGE_ADB_PERSIST_KEY_STORE = 8; + static final int MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME = 9; + + private AdbKeyStore mAdbKeyStore; AdbDebuggingHandler(Looper looper) { super(looper); + mAdbKeyStore = new AdbKeyStore(); + } + + /** + * Constructor that accepts the AdbDebuggingThread to which responses should be sent + * and the AdbKeyStore to be used to store the temporary grants. + */ + @TestApi + AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) { + super(looper); + mThread = thread; + mAdbKeyStore = adbKeyStore; } public void handleMessage(Message msg) { @@ -251,12 +315,15 @@ public class AdbDebuggingManager { break; } - if (msg.arg1 == 1) { - writeKey(key); - } - + boolean alwaysAllow = msg.arg1 == 1; if (mThread != null) { mThread.sendResponse("OK"); + if (alwaysAllow) { + mConnectedKey = key; + mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); + scheduleJobToUpdateAdbKeyStore(); + } + logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow); } break; } @@ -264,36 +331,88 @@ public class AdbDebuggingManager { case MESSAGE_ADB_DENY: if (mThread != null) { mThread.sendResponse("NO"); + logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false); } break; case MESSAGE_ADB_CONFIRM: { + String key = (String) msg.obj; if ("trigger_restart_min_framework".equals( SystemProperties.get("vold.decrypt"))) { Slog.d(TAG, "Deferring adb confirmation until after vold decrypt"); if (mThread != null) { mThread.sendResponse("NO"); + logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false); } break; } - String key = (String) msg.obj; String fingerprints = getFingerprints(key); if ("".equals(fingerprints)) { if (mThread != null) { mThread.sendResponse("NO"); + logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false); } break; } - mFingerprints = fingerprints; - startConfirmation(key, mFingerprints); + // Check if the key should be allowed without user interaction. + if (mAdbKeyStore.isKeyAuthorized(key)) { + if (mThread != null) { + mThread.sendResponse("OK"); + mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); + logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true); + mConnectedKey = key; + scheduleJobToUpdateAdbKeyStore(); + } + } else { + logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false); + mFingerprints = fingerprints; + startConfirmation(key, mFingerprints); + } break; } - case MESSAGE_ADB_CLEAR: + case MESSAGE_ADB_CLEAR: { deleteKeyFile(); + mConnectedKey = null; + mAdbKeyStore.deleteKeyStore(); + cancelJobToUpdateAdbKeyStore(); + break; + } + + case MESSAGE_ADB_DISCONNECT: { + if (mConnectedKey != null) { + mAdbKeyStore.setLastConnectionTime(mConnectedKey, + System.currentTimeMillis()); + cancelJobToUpdateAdbKeyStore(); + } + logAdbConnectionChanged(mConnectedKey, AdbProtoEnums.DISCONNECTED, + (mConnectedKey != null)); + mConnectedKey = null; + break; + } + + case MESSAGE_ADB_PERSIST_KEY_STORE: { + mAdbKeyStore.persistKeyStore(); break; + } + + case MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME: { + if (mConnectedKey != null) { + mAdbKeyStore.setLastConnectionTime(mConnectedKey, + System.currentTimeMillis()); + scheduleJobToUpdateAdbKeyStore(); + } + break; + } } } + + private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) { + long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key); + long authWindow = mAdbKeyStore.getAllowedConnectionTime(); + StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state, + alwaysAllow); + } } private String getFingerprints(String key) { @@ -335,7 +454,8 @@ public class AdbDebuggingManager { UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId); String componentString; if (userInfo.isAdmin()) { - componentString = Resources.getSystem().getString( + componentString = mConfirmComponent != null + ? mConfirmComponent : Resources.getSystem().getString( com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent); } else { // If the current foreground user is not the admin user we send a different @@ -397,7 +517,10 @@ public class AdbDebuggingManager { return intent; } - private File getUserKeyFile() { + /** + * Returns a new File with the specified name in the adb directory. + */ + private File getAdbFile(String fileName) { File dataDir = Environment.getDataDirectory(); File adbDir = new File(dataDir, ADB_DIRECTORY); @@ -406,7 +529,11 @@ public class AdbDebuggingManager { return null; } - return new File(adbDir, ADB_KEYS_FILE); + return new File(adbDir, fileName); + } + + private File getUserKeyFile() { + return getAdbFile(ADB_KEYS_FILE); } private void writeKey(String key) { @@ -476,6 +603,36 @@ public class AdbDebuggingManager { } /** + * Sends a message to the handler to persist the key store. + */ + private void sendPersistKeyStoreMessage() { + Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE); + mHandler.sendMessage(msg); + } + + /** + * Schedules a job to update the connection time of the currently connected key. This is + * intended for cases such as development devices that are left connected to a user's + * system beyond the window within which a connection is allowed without user interaction. + * A job should be rescheduled daily so that if the device is rebooted while connected to + * the user's system the last time in the key store will show within 24 hours which should + * be within the allowed window. + */ + private void scheduleJobToUpdateAdbKeyStore() { + Message message = mHandler.obtainMessage( + AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME); + mHandler.sendMessageDelayed(message, AdbDebuggingHandler.UPDATE_KEY_STORE_JOB_INTERVAL); + } + + /** + * Cancels the scheduled job to update the connection time of the currently connected key. + * This should be invoked once the adb session is disconnected. + */ + private void cancelJobToUpdateAdbKeyStore() { + mHandler.removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME); + } + + /** * Dump the USB debugging state. */ public void dump(DualDumpOutputStream dump, String idName, long id) { @@ -501,4 +658,231 @@ public class AdbDebuggingManager { dump.end(token); } + + /** + * Handles adb keys for which the user has granted the 'always allow' option. This class ensures + * these grants are revoked after a period of inactivity as specified in the + * ADB_ALLOWED_CONNECTION_TIME setting. + */ + class AdbKeyStore { + private Map<String, Long> mKeyMap; + private File mKeyFile; + private AtomicFile mAtomicKeyFile; + // This file contains keys that will be allowed to connect without user interaction as long + // as a subsequent connection occurs within the allowed duration. + private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml"; + private static final String XML_TAG_ADB_KEY = "adbKey"; + private static final String XML_ATTRIBUTE_KEY = "key"; + private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection"; + + /** + * Value returned by {@code getLastConnectionTime} when there is no previously saved + * connection time for the specified key. + */ + public static final long NO_PREVIOUS_CONNECTION = 0; + + /** + * Constructor that uses the default location for the persistent adb key store. + */ + AdbKeyStore() { + initKeyFile(); + mKeyMap = getKeyMapFromFile(); + } + + /** + * Constructor that uses the specified file as the location for the persistent adb key + * store. + */ + AdbKeyStore(File keyFile) { + mKeyFile = keyFile; + initKeyFile(); + mKeyMap = getKeyMapFromFile(); + } + + /** + * Initializes the key file that will be used to persist the adb grants. + */ + private void initKeyFile() { + if (mKeyFile == null) { + mKeyFile = getAdbFile(ADB_TEMP_KEYS_FILE); + } + // getAdbFile can return null if the adb file cannot be obtained + if (mKeyFile != null) { + mAtomicKeyFile = new AtomicFile(mKeyFile); + } + } + + /** + * Returns the key map with the keys and last connection times from the key file. + */ + private Map<String, Long> getKeyMapFromFile() { + Map<String, Long> keyMap = new HashMap<String, Long>(); + // if the AtomicFile could not be instantiated before attempt again; if it still fails + // return an empty key map. + if (mAtomicKeyFile == null) { + initKeyFile(); + if (mAtomicKeyFile == null) { + Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); + return keyMap; + } + } + if (!mAtomicKeyFile.exists()) { + return keyMap; + } + try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(keyStream, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY); + while (parser.next() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if (tagName == null) { + break; + } else if (!tagName.equals(XML_TAG_ADB_KEY)) { + XmlUtils.skipCurrentTag(parser); + continue; + } + String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY); + long connectionTime; + try { + connectionTime = Long.valueOf( + parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION)); + } catch (NumberFormatException e) { + Slog.e(TAG, + "Caught a NumberFormatException parsing the last connection time: " + + e); + XmlUtils.skipCurrentTag(parser); + continue; + } + keyMap.put(key, connectionTime); + } + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Caught an exception parsing the XML key file: ", e); + } + return keyMap; + } + + /** + * Writes the key map to the key file. + */ + public void persistKeyStore() { + // if there is nothing in the key map then ensure any keys left in the key store files + // are deleted as well. + if (mKeyMap.size() == 0) { + deleteKeyStore(); + return; + } + if (mAtomicKeyFile == null) { + initKeyFile(); + if (mAtomicKeyFile == null) { + Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing"); + return; + } + } + FileOutputStream keyStream = null; + try { + XmlSerializer serializer = new FastXmlSerializer(); + keyStream = mAtomicKeyFile.startWrite(); + serializer.setOutput(keyStream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + long allowedTime = getAllowedConnectionTime(); + long systemTime = System.currentTimeMillis(); + Iterator keyMapIterator = mKeyMap.entrySet().iterator(); + while (keyMapIterator.hasNext()) { + Map.Entry<String, Long> keyEntry = (Map.Entry) keyMapIterator.next(); + long connectionTime = keyEntry.getValue(); + if (systemTime < (connectionTime + allowedTime)) { + serializer.startTag(null, XML_TAG_ADB_KEY); + serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey()); + serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION, + String.valueOf(keyEntry.getValue())); + serializer.endTag(null, XML_TAG_ADB_KEY); + } else { + keyMapIterator.remove(); + } + } + serializer.endDocument(); + mAtomicKeyFile.finishWrite(keyStream); + } catch (IOException e) { + Slog.e(TAG, "Caught an exception writing the key map: ", e); + mAtomicKeyFile.failWrite(keyStream); + } + } + + /** + * Removes all of the entries in the key map and deletes the key file. + */ + public void deleteKeyStore() { + mKeyMap.clear(); + if (mAtomicKeyFile == null) { + return; + } + mAtomicKeyFile.delete(); + } + + /** + * Returns the time of the last connection from the specified key, or {@code + * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant. + */ + public long getLastConnectionTime(String key) { + return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION); + } + + /** + * Sets the time of the last connection for the specified key to the provided time. + */ + public void setLastConnectionTime(String key, long connectionTime) { + // Do not set the connection time to a value that is earlier than what was previously + // stored as the last connection time. + if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime) { + return; + } + mKeyMap.put(key, connectionTime); + sendPersistKeyStoreMessage(); + } + + /** + * Returns whether the specified key should be authroized to connect without user + * interaction. This requires that the user previously connected this device and selected + * the option to 'Always allow', and the time since the last connection is within the + * allowed window. + */ + public boolean isKeyAuthorized(String key) { + long lastConnectionTime = getLastConnectionTime(key); + if (lastConnectionTime == NO_PREVIOUS_CONNECTION) { + return false; + } + long allowedConnectionTime = getAllowedConnectionTime(); + // if the allowed connection time is 0 then revert to the previous behavior of always + // allowing previously granted adb grants. + if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime + + allowedConnectionTime))) { + return true; + } else { + // since this key is no longer auhorized remove it from the Map + removeKey(key); + return false; + } + } + + /** + * Returns the connection time within which a connection from an allowed key is + * automatically allowed without user interaction. + */ + public long getAllowedConnectionTime() { + return Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.ADB_ALLOWED_CONNECTION_TIME, + Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME); + } + + /** + * Removes the specified key from the key store. + */ + public void removeKey(String key) { + if (!mKeyMap.containsKey(key)) { + return; + } + mKeyMap.remove(key); + sendPersistKeyStoreMessage(); + } + } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index ac392a1a44d0..5d6c2f074f9d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -82,7 +82,6 @@ import android.util.proto.ProtoOutputStream; import android.webkit.WebViewZygote; import com.android.internal.R; -import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; @@ -169,8 +168,6 @@ public final class ActiveServices { /** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */ private ArrayList<ServiceRecord> mTmpCollectionResults = null; - private int mNumServiceRestarts = 0; - /** * For keeping ActiveForegroundApps retaining state while the screen is off. */ @@ -680,6 +677,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -1652,6 +1650,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName); intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback); @@ -2330,50 +2329,19 @@ public final class ActiveServices { r.restartDelay = mAm.mConstants.BOUND_SERVICE_CRASH_RESTART_DURATION * (r.crashCount - 1); } else { - mNumServiceRestarts++; - if (!mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS) { - // If it has been a "reasonably long time" since the service - // was started, then reset our restart duration back to - // the beginning, so we don't infinitely increase the duration - // on a service that just occasionally gets killed (which is - // a normal case, due to process being killed to reclaim memory). - if (now > (r.restartTime + resetTime)) { - r.restartCount = 1; - r.restartDelay = minDuration; - } else { - r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; - } - } + // If it has been a "reasonably long time" since the service + // was started, then reset our restart duration back to + // the beginning, so we don't infinitely increase the duration + // on a service that just occasionally gets killed (which is + // a normal case, due to process being killed to reclaim memory). + if (now > (r.restartTime+resetTime)) { + r.restartCount = 1; + r.restartDelay = minDuration; } else { - // The service will be restarted based on the last known oom_adj value - // for the process when it was destroyed. A higher oom_adj value - // means that the service will be restarted quicker. - // If the service seems to keep crashing, the service restart counter will be - // incremented and the restart delay will have an exponential backoff. - final int lastOomAdj = r.app == null ? r.lastKnownOomAdj : r.app.setAdj; - if (r.restartCount > 1) { - r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; - } - } else { - if (lastOomAdj <= ProcessList.VISIBLE_APP_ADJ) { - r.restartDelay = 1 * 1000; // 1 second - } else if (lastOomAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { - r.restartDelay = 2 * 1000; // 2 seconds - } else if (lastOomAdj <= ProcessList.SERVICE_ADJ) { - r.restartDelay = 5 * 1000; // 5 seconds - } else if (lastOomAdj <= ProcessList.PREVIOUS_APP_ADJ) { - r.restartDelay = 10 * 1000; // 10 seconds - } else { - r.restartDelay = 20 * 1000; // 20 seconds - } + r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; } - // If the last time the service restarted was more than a minute ago, - // reset the service restart count, otherwise increment by one. - r.restartCount = (now > (r.restartTime + resetTime)) ? 1 : (r.restartCount + 1); } } @@ -2381,7 +2349,21 @@ public final class ActiveServices { // Make sure that we don't end up restarting a bunch of services // all at the same time. - ensureSpacedServiceRestarts(r, now); + boolean repeat; + do { + repeat = false; + final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN; + for (int i=mRestartingServices.size()-1; i>=0; i--) { + ServiceRecord r2 = mRestartingServices.get(i); + if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime-restartTimeBetween) + && r.nextRestartTime < (r2.nextRestartTime+restartTimeBetween)) { + r.nextRestartTime = r2.nextRestartTime + restartTimeBetween; + r.restartDelay = r.nextRestartTime - now; + repeat = true; + break; + } + } + } while (repeat); } else { // Persistent processes are immediately restarted, so there is no @@ -2411,24 +2393,6 @@ public final class ActiveServices { return canceled; } - private void ensureSpacedServiceRestarts(ServiceRecord r, long now) { - boolean repeat; - do { - repeat = false; - final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN; - for (int i = mRestartingServices.size() - 1; i >= 0; i--) { - ServiceRecord r2 = mRestartingServices.get(i); - if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime - restartTimeBetween) - && r.nextRestartTime < (r2.nextRestartTime + restartTimeBetween)) { - r.nextRestartTime = r2.nextRestartTime + restartTimeBetween; - r.restartDelay = r.nextRestartTime - now; - repeat = true; - break; - } - } - } while (repeat); - } - final void performServiceRestartLocked(ServiceRecord r) { if (!mRestartingServices.contains(r)) { return; @@ -2503,26 +2467,6 @@ public final class ActiveServices { return null; } - // If the service is restarting, check the memory pressure - if it's low or critical, then - // further delay the restart because it will most likely get killed again; if the pressure - // is not low or critical, continue restarting. - if (mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS && whileRestarting) { - final int memLevel = mAm.getMemoryTrimLevel(); - if (memLevel >= ProcessStats.ADJ_MEM_FACTOR_LOW) { - final long now = SystemClock.uptimeMillis(); - // Delay the restart based on the current memory pressure - // Default delay duration is 5 seconds - r.restartDelay = memLevel * mAm.mConstants.SERVICE_RESTART_DELAY_DURATION; - r.nextRestartTime = now + r.restartDelay; - ensureSpacedServiceRestarts(r, now); - mAm.mHandler.removeCallbacks(r.restarter); - mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime); - Slog.w(TAG, "Delaying restart of crashed service " + r.shortInstanceName - + " in " + r.restartDelay + "ms due to continued memory pressure"); - return null; - } - } - if (DEBUG_SERVICE) { Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired); } @@ -3637,7 +3581,6 @@ public final class ActiveServices { || !mAm.mUserController.isUserRunning(sr.userId, 0)) { bringDownServiceLocked(sr); } else { - sr.lastKnownOomAdj = app.setAdj; boolean canceled = scheduleServiceRestartLocked(sr, true); // Should the service remain running? Note that in the diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 546975a65975..d025d739055b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,13 +16,19 @@ package com.android.server.am; +import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; +import android.app.ActivityThread; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; @@ -34,7 +40,6 @@ import java.io.PrintWriter; final class ActivityManagerConstants extends ContentObserver { // Key names stored in the settings value. - private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; private static final String KEY_FGSERVICE_MIN_SHOWN_TIME = "fgservice_min_shown_time"; @@ -69,15 +74,6 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; - static final String KEY_USE_COMPACTION = "use_compaction"; - static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; - static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; - static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; - static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; - static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; - static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; - static final String KEY_USE_MEM_AWARE_SERVICE_RESTARTS = "use_mem_aware_service_restarts"; - static final String KEY_SERVICE_RESTART_DELAY_DURATION = "service_restart_delay_duration"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -108,15 +104,6 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; - private static final boolean DEFAULT_USE_COMPACTION = false; - public static final int DEFAULT_COMPACT_ACTION_1 = 1; - public static final int DEFAULT_COMPACT_ACTION_2 = 3; - public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000; - public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000; - public static final long DEFAULT_COMPACT_THROTTLE_3 = 500; - public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000; - private static final boolean DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS = true; - private static final long DEFAULT_SERVICE_RESTART_DELAY_DURATION = 5 * 1000; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -236,29 +223,6 @@ final class ActivityManagerConstants extends ContentObserver { // this long. public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION; - // Use compaction for background apps. - public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION; - - // Action for compactAppSome. - public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1; - // Action for compactAppFull; - public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2; - - // How long we'll skip second compactAppSome after first compactAppSome - public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1; - // How long we'll skip compactAppSome after compactAppFull - public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2; - // How long we'll skip compactAppFull after compactAppSome - public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3; - // How long we'll skip second compactAppFull after first compactAppFull - public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4; - - // Use memory aware optimized logic to restart services - public boolean FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS; - - // How long a service restart will be delayed by if there is memory pressure - public long SERVICE_RESTART_DELAY_DURATION = DEFAULT_SERVICE_RESTART_DELAY_DURATION; - // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -305,10 +269,19 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.getUriFor( Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED); + private final OnPropertyChangedListener mOnDeviceConfigChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + if (KEY_MAX_CACHED_PROCESSES.equals(name)) { + updateMaxCachedProcesses(); + } + } + }; + public ActivityManagerConstants(ActivityManagerService service, Handler handler) { super(handler); mService = service; - updateMaxCachedProcesses(); } public void start(ContentResolver resolver) { @@ -319,6 +292,11 @@ final class ActivityManagerConstants extends ContentObserver { updateConstants(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStartsEnabled(); + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), + mOnDeviceConfigChangedListener); + updateMaxCachedProcesses(); + } public void setOverrideMaxCachedProcesses(int value) { @@ -357,8 +335,6 @@ final class ActivityManagerConstants extends ContentObserver { // with defaults. Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e); } - MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES, - DEFAULT_MAX_CACHED_PROCESSES); BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME, DEFAULT_BACKGROUND_SETTLE_TIME); FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME, @@ -416,17 +392,9 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MEMORY_INFO_THROTTLE_TIME); TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION, DEFAULT_TOP_TO_FGS_GRACE_DURATION); - USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION); - COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1); - COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2); - COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1); - COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2); - COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3); - COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4); - FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = mParser.getBoolean( - KEY_USE_MEM_AWARE_SERVICE_RESTARTS, DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS); - SERVICE_RESTART_DELAY_DURATION = mParser.getLong( - KEY_SERVICE_RESTART_DELAY_DURATION, DEFAULT_SERVICE_RESTART_DELAY_DURATION); + + // For new flags that are intended for server-side experiments, please use the new + // DeviceConfig package. updateMaxCachedProcesses(); } @@ -443,8 +411,19 @@ final class ActivityManagerConstants extends ContentObserver { } private void updateMaxCachedProcesses() { - CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 - ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses; + String maxCachedProcessesFlag = DeviceConfig.getProperty( + DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES); + try { + CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 + ? (TextUtils.isEmpty(maxCachedProcessesFlag) + ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag)) + : mOverrideMaxCachedProcesses; + } catch (NumberFormatException e) { + // Bad flag value from Phenotype, revert to default. + Slog.e("ActivityManagerConstants", + "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); + CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; + } CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); // Note the trim levels do NOT depend on the override process limit, we want @@ -517,12 +496,6 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(MEMORY_INFO_THROTTLE_TIME); pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("="); pw.println(TOP_TO_FGS_GRACE_DURATION); - pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("="); - pw.println(USE_COMPACTION); - pw.print(" "); pw.print(KEY_USE_MEM_AWARE_SERVICE_RESTARTS); pw.print("="); - pw.println(FLAG_USE_MEM_AWARE_SERVICE_RESTARTS); - pw.print(" "); pw.print(KEY_SERVICE_RESTART_DELAY_DURATION); pw.print("="); - pw.println(SERVICE_RESTART_DELAY_DURATION); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 089847d1ff7f..eb643b670fcd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -64,6 +64,7 @@ import static android.os.Process.SHELL_UID; import static android.os.Process.SIGNAL_USR1; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.os.Process.ZYGOTE_PROCESS; import static android.os.Process.getTotalMemory; import static android.os.Process.isThreadInProcess; import static android.os.Process.killProcess; @@ -75,7 +76,6 @@ import static android.os.Process.removeAllProcessGroups; import static android.os.Process.sendSignal; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; -import static android.os.Process.zygoteProcess; import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES; import static android.provider.Settings.Global.DEBUG_APP; import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS; @@ -1607,19 +1607,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } break; case UPDATE_HTTP_PROXY_MSG: { - ProxyInfo proxy = (ProxyInfo)msg.obj; - String host = ""; - String port = ""; - String exclList = ""; - Uri pacFileUrl = Uri.EMPTY; - if (proxy != null) { - host = proxy.getHost(); - port = Integer.toString(proxy.getPort()); - exclList = proxy.getExclusionListAsString(); - pacFileUrl = proxy.getPacFileUrl(); - } synchronized (ActivityManagerService.this) { - mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl); + mProcessList.setAllHttpProxyLocked(); } } break; case PROC_START_TIMEOUT_MSG: { @@ -2162,7 +2151,7 @@ public class ActivityManagerService extends IActivityManager.Stub ? Collections.emptyList() : Arrays.asList(exemptions.split(",")); } - if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) { + if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) { Slog.e(TAG, "Failed to set API blacklist exemptions!"); // leave mExemptionsStr as is, so we don't try to send the same list again. mExemptions = Collections.emptyList(); @@ -2175,7 +2164,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (logSampleRate != -1 && logSampleRate != mLogSampleRate) { mLogSampleRate = logSampleRate; - zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate); + ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate); } mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY); } @@ -2864,16 +2853,18 @@ public class ActivityManagerService extends IActivityManager.Stub * @param userId * @param event * @param appToken ActivityRecord's appToken. + * @param taskRoot TaskRecord's root */ public void updateActivityUsageStats(ComponentName activity, int userId, int event, - IBinder appToken) { + IBinder appToken, ComponentName taskRoot) { if (DEBUG_SWITCH) { Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp=" + activity + " hash=" + appToken.hashCode() + " event=" + event); } synchronized (this) { if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode()); + mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(), + taskRoot); } } } @@ -2911,7 +2902,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (mUsageStatsService != null) { mUsageStatsService.reportEvent(service, userId, started ? UsageEvents.Event.FOREGROUND_SERVICE_START - : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0); + : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null); } } } @@ -4880,7 +4871,7 @@ public class ActivityManagerService extends IActivityManager.Stub ArraySet<String> completedIsas = new ArraySet<String>(); for (String abi : Build.SUPPORTED_ABIS) { - zygoteProcess.establishZygoteConnectionForAbi(abi); + ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi); final String instructionSet = VMRuntime.getInstructionSet(abi); if (!completedIsas.contains(instructionSet)) { try { @@ -7226,6 +7217,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.installSystemProviders(); mDevelopmentSettingsObserver = new DevelopmentSettingsObserver(); SettingsToPropertiesMapper.start(mContext.getContentResolver()); + mOomAdjuster.initSettings(); // Now that the settings provider is published we can consider sending // in a rescue party. @@ -9338,6 +9330,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -9737,6 +9730,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else if ("settings".equals(cmd)) { synchronized (this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); } } else if ("services".equals(cmd) || "s".equals(cmd)) { if (dumpClient) { @@ -14654,8 +14648,7 @@ public class ActivityManagerService extends IActivityManager.Stub mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); break; case Proxy.PROXY_CHANGE_ACTION: - ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG)); break; case android.hardware.Camera.ACTION_NEW_PICTURE: case android.hardware.Camera.ACTION_NEW_VIDEO: @@ -17521,10 +17514,10 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void updateActivityUsageStats(ComponentName activity, int userId, int event, - IBinder appToken) { + IBinder appToken, ComponentName taskRoot) { synchronized (ActivityManagerService.this) { ActivityManagerService.this.updateActivityUsageStats(activity, userId, event, - appToken); + appToken, taskRoot); } } diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index fd402cc08c0c..bb55ec3100c0 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -16,34 +16,63 @@ package com.android.server.am; -import com.android.internal.annotations.GuardedBy; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; import android.app.ActivityManager; - +import android.app.ActivityThread; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.Trace; - +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; +import android.text.TextUtils; import android.util.EventLog; import android.util.StatsLog; -import static android.os.Process.THREAD_PRIORITY_FOREGROUND; - +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceThread; import java.io.FileOutputStream; -import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; public final class AppCompactor { - /** - * Processes to compact. - */ - final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>(); + // Phenotype sends int configurations and we map them to the strings we'll use on device, + // preventing a weird string value entering the kernel. + private static final int COMPACT_ACTION_FILE_FLAG = 1; + private static final int COMPACT_ACTION_ANON_FLAG = 2; + private static final int COMPACT_ACTION_FULL_FLAG = 3; + private static final String COMPACT_ACTION_FILE = "file"; + private static final String COMPACT_ACTION_ANON = "anon"; + private static final String COMPACT_ACTION_FULL = "all"; + + // Defaults for phenotype flags. + @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000; + + @VisibleForTesting + interface PropertyChangedCallbackForTest { + void onPropertyChanged(); + } + private PropertyChangedCallbackForTest mTestCallback; + + // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; static final int COMPACT_PROCESS_MSG = 1; @@ -57,70 +86,106 @@ public final class AppCompactor { */ final ServiceThread mCompactionThread; - final private Handler mCompactionHandler; - - final private ActivityManagerService mAm; - final private ActivityManagerConstants mConstants; - - final private String COMPACT_ACTION_FILE = "file"; - final private String COMPACT_ACTION_ANON = "anon"; - final private String COMPACT_ACTION_FULL = "all"; - - final private String compactActionSome; - final private String compactActionFull; - - final private long throttleSomeSome; - final private long throttleSomeFull; - final private long throttleFullSome; - final private long throttleFullFull; + private final ArrayList<ProcessRecord> mPendingCompactionProcesses = + new ArrayList<ProcessRecord>(); + private final ActivityManagerService mAm; + private final OnPropertyChangedListener mOnFlagsChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + synchronized (mPhenotypeFlagLock) { + if (KEY_USE_COMPACTION.equals(name)) { + updateUseCompaction(); + } else if (KEY_COMPACT_ACTION_1.equals(name) + || KEY_COMPACT_ACTION_2.equals(name)) { + updateCompactionActions(); + } else if (KEY_COMPACT_THROTTLE_1.equals(name) + || KEY_COMPACT_THROTTLE_2.equals(name) + || KEY_COMPACT_THROTTLE_3.equals(name) + || KEY_COMPACT_THROTTLE_4.equals(name)) { + updateCompactionThrottles(); + } + } + if (mTestCallback != null) { + mTestCallback.onPropertyChanged(); + } + } + }; + + private final Object mPhenotypeFlagLock = new Object(); + + // Configured by phenotype. Updates from the server take effect immediately. + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionSome = + compactActionIntToString(DEFAULT_COMPACT_ACTION_1); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionFull = + compactActionIntToString(DEFAULT_COMPACT_ACTION_2); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + @GuardedBy("mPhenotypeFlagLock") + private boolean mUseCompaction = DEFAULT_USE_COMPACTION; + + // Handler on which compaction runs. + private Handler mCompactionHandler; public AppCompactor(ActivityManagerService am) { mAm = am; - mConstants = am.mConstants; - mCompactionThread = new ServiceThread("CompactionThread", THREAD_PRIORITY_FOREGROUND, true); - mCompactionThread.start(); - mCompactionHandler = new MemCompactionHandler(this); - - switch(mConstants.COMPACT_ACTION_1) { - case 1: - compactActionSome = COMPACT_ACTION_FILE; - break; - case 2: - compactActionSome = COMPACT_ACTION_ANON; - break; - case 3: - compactActionSome = COMPACT_ACTION_FULL; - break; - default: - compactActionSome = COMPACT_ACTION_FILE; - break; + } + + @VisibleForTesting + AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) { + this(am); + mTestCallback = callback; + } + + /** + * Reads phenotype config to determine whether app compaction is enabled or not and + * starts the background thread if necessary. + */ + public void init() { + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener); + synchronized (mPhenotypeFlagLock) { + updateUseCompaction(); + updateCompactionActions(); + updateCompactionThrottles(); } + } - switch(mConstants.COMPACT_ACTION_2) { - case 1: - compactActionFull = COMPACT_ACTION_FILE; - break; - case 2: - compactActionFull = COMPACT_ACTION_ANON; - break; - case 3: - compactActionFull = COMPACT_ACTION_FULL; - break; - default: - compactActionFull = COMPACT_ACTION_FULL; - break; + /** + * Returns whether compaction is enabled. + */ + public boolean useCompaction() { + synchronized (mPhenotypeFlagLock) { + return mUseCompaction; } + } - throttleSomeSome = mConstants.COMPACT_THROTTLE_1; - throttleSomeFull = mConstants.COMPACT_THROTTLE_2; - throttleFullSome = mConstants.COMPACT_THROTTLE_3; - throttleFullFull = mConstants.COMPACT_THROTTLE_4; + @GuardedBy("mAm") + void dump(PrintWriter pw) { + pw.println("AppCompactor settings"); + synchronized (mPhenotypeFlagLock) { + pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction); + pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome); + pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull); + pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome); + pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); + pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); + pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); + } } - // Must be called while holding AMS lock. - final void compactAppSome(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppSome(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -128,8 +193,8 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - // Must be called while holding AMS lock. - final void compactAppFull(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppFull(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -137,97 +202,205 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - final class MemCompactionHandler extends Handler { - AppCompactor mAc; - private MemCompactionHandler(AppCompactor ac) { - super(ac.mCompactionThread.getLooper()); - mAc = ac; + /** + * Reads the flag value from DeviceConfig to determine whether app compaction + * should be enabled, and starts/stops the compaction thread as needed. + */ + @GuardedBy("mPhenotypeFlagLock") + private void updateUseCompaction() { + String useCompactionFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION); + mUseCompaction = TextUtils.isEmpty(useCompactionFlag) + ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag); + if (mUseCompaction && !mCompactionThread.isAlive()) { + mCompactionThread.start(); + mCompactionHandler = new MemCompactionHandler(); + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionActions() { + String compactAction1Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1); + String compactAction2Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2); + + int compactAction1 = DEFAULT_COMPACT_ACTION_1; + try { + compactAction1 = TextUtils.isEmpty(compactAction1Flag) + ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + int compactAction2 = DEFAULT_COMPACT_ACTION_2; + try { + compactAction2 = TextUtils.isEmpty(compactAction2Flag) + ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + mCompactActionSome = compactActionIntToString(compactAction1); + mCompactActionFull = compactActionIntToString(compactAction2); + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionThrottles() { + boolean useThrottleDefaults = false; + String throttleSomeSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1); + String throttleSomeFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2); + String throttleFullSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3); + String throttleFullFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4); + + if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag) + || TextUtils.isEmpty(throttleFullSomeFlag) + || TextUtils.isEmpty(throttleFullFullFlag)) { + // Set defaults for all if any are not set. + useThrottleDefaults = true; + } else { + try { + mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag); + mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag); + mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag); + mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag); + } catch (NumberFormatException e) { + useThrottleDefaults = true; + } + } + + if (useThrottleDefaults) { + mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + } + } + + @VisibleForTesting + static String compactActionIntToString(int action) { + switch(action) { + case COMPACT_ACTION_FILE_FLAG: + return COMPACT_ACTION_FILE; + case COMPACT_ACTION_ANON_FLAG: + return COMPACT_ACTION_ANON; + case COMPACT_ACTION_FULL_FLAG: + return COMPACT_ACTION_FULL; + default: + return COMPACT_ACTION_FILE; + } + } + + private final class MemCompactionHandler extends Handler { + private MemCompactionHandler() { + super(mCompactionThread.getLooper()); } @Override public void handleMessage(Message msg) { switch (msg.what) { - case COMPACT_PROCESS_MSG: { - long start = SystemClock.uptimeMillis(); - ProcessRecord proc; - int pid; - String action; - final String name; - int pendingAction, lastCompactAction; - long lastCompactTime; - synchronized(mAc.mAm) { - proc = mAc.mPendingCompactionProcesses.remove(0); - - // don't compact if the process has returned to perceptible - if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { - return; + case COMPACT_PROCESS_MSG: { + long start = SystemClock.uptimeMillis(); + ProcessRecord proc; + int pid; + String action; + final String name; + int pendingAction, lastCompactAction; + long lastCompactTime; + synchronized (mAm) { + proc = mPendingCompactionProcesses.remove(0); + + // don't compact if the process has returned to perceptible + if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { + return; + } + + pid = proc.pid; + name = proc.processName; + pendingAction = proc.reqCompactAction; + lastCompactAction = proc.lastCompactAction; + lastCompactTime = proc.lastCompactTime; } - pid = proc.pid; - name = proc.processName; - pendingAction = proc.reqCompactAction; - lastCompactAction = proc.lastCompactAction; - lastCompactTime = proc.lastCompactTime; - } - if (pid == 0) { - // not a real process, either one being launched or one being killed - return; - } - - // basic throttling - // use the ActivityManagerConstants knobs to determine whether current/prevous - // compaction combo should be throtted or not - if (pendingAction == COMPACT_PROCESS_SOME) { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) { + if (pid == 0) { + // not a real process, either one being launched or one being killed return; } - } else { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) { - return; + + // basic throttling + // use the Phenotype flag knobs to determine whether current/prevous + // compaction combo should be throtted or not + + // Note that we explicitly don't take mPhenotypeFlagLock here as the flags + // should very seldom change, and taking the risk of using the wrong action is + // preferable to taking the lock for every single compaction action. + if (pendingAction == COMPACT_PROCESS_SOME) { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleSomeSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleSomeFull))) { + return; + } + } else { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleFullSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleFullFull))) { + return; + } } - } - try { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + - ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + - ": " + name); - long[] rssBefore = Process.getRss(pid); - FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); if (pendingAction == COMPACT_PROCESS_SOME) { - action = compactActionSome; + action = mCompactActionSome; } else { - action = compactActionFull; + action = mCompactActionFull; } - fos.write(action.getBytes()); - fos.close(); - long[] rssAfter = Process.getRss(pid); - long end = SystemClock.uptimeMillis(); - long time = end - start; - EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); - StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, - ActivityManager.processStateAmToProto(msg.arg2)); - synchronized(mAc.mAm) { - proc.lastCompactTime = end; - proc.lastCompactAction = pendingAction; + + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + + ": " + name); + long[] rssBefore = Process.getRss(pid); + FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); + fos.write(action.getBytes()); + fos.close(); + long[] rssAfter = Process.getRss(pid); + long end = SystemClock.uptimeMillis(); + long time = end - start; + EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); + StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, + ActivityManager.processStateAmToProto(msg.arg2)); + synchronized (mAm) { + proc.lastCompactTime = end; + proc.lastCompactAction = pendingAction; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } catch (Exception e) { + // nothing to do, presumably the process died + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } catch (Exception e) { - // nothing to do, presumably the process died - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } - } } } - - } diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index aabb5877764e..dc9a4bf1ad94 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -16,8 +16,6 @@ package com.android.server.am; -import com.android.internal.R; - import android.app.AlertDialog; import android.content.Context; import android.os.Handler; @@ -26,6 +24,8 @@ import android.view.KeyEvent; import android.view.WindowManager; import android.widget.Button; +import com.android.internal.R; + public class BaseErrorDialog extends AlertDialog { private static final int ENABLE_BUTTONS = 0; private static final int DISABLE_BUTTONS = 1; @@ -36,7 +36,7 @@ public class BaseErrorDialog extends AlertDialog { super(context, com.android.internal.R.style.Theme_DeviceDefault_Dialog_AppError); context.assertRuntimeOverlayThemable(); - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); WindowManager.LayoutParams attrs = getWindow().getAttributes(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index a376e7a15410..08900328a200 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.app.ActivityManager; import android.app.job.JobProtoEnums; import android.bluetooth.BluetoothActivityEnergyInfo; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -46,6 +47,7 @@ import android.os.connectivity.WifiBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; import android.os.health.UidHealthStats; +import android.provider.Settings; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; @@ -1651,4 +1653,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub return new HealthStatsParceler(uidWriter); } + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * + * @hide + */ + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null); + final long ident = Binder.clearCallingIdentity(); + + try { + final ContentResolver contentResolver = mContext.getContentResolver(); + return Settings.Global.putLong(contentResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + delayMillis); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 353749f211c1..cdf6e0ede865 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -776,6 +776,7 @@ public final class BroadcastQueue { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index d3953b58296c..3d69aa8088f5 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -61,6 +61,8 @@ final class CoreSettingsObserver extends ContentObserver { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class); sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index be910d46a8ad..1e03f6c3ba5f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -134,10 +134,11 @@ public final class OomAdjuster { mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); mConstants = mService.mConstants; - // mConstants can be null under test, which causes AppCompactor to crash - if (mConstants != null) { - mAppCompact = new AppCompactor(mService); - } + mAppCompact = new AppCompactor(mService); + } + + void initSettings() { + mAppCompact.init(); } /** @@ -1679,7 +1680,7 @@ public final class OomAdjuster { if (app.curAdj != app.setAdj) { // don't compact during bootup - if (mConstants.USE_COMPACTION && mService.mBooted) { + if (mAppCompact.useCompaction() && mService.mBooted) { // Perform a minor compaction when a perceptible app becomes the prev/home app // Perform a major compaction when any app enters cached // reminder: here, setAdj is previous state, curAdj is upcoming state @@ -2104,4 +2105,8 @@ public final class OomAdjuster { + " mNewNumServiceProcs=" + mNewNumServiceProcs); } + @GuardedBy("mService") + void dumpAppCompactorSettings(PrintWriter pw) { + mAppCompact.dump(pw); + } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 55cca950a213..0b27a8ad77dc 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,6 +379,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { if (userId == UserHandle.USER_CURRENT) { userId = controller.mUserController.getCurrentOrTargetUserId(); } + // temporarily allow receivers and services to open activities from background if the + // PendingIntent.send() caller was foreground at the time of sendInner() call + final boolean allowTrampoline = uid != callingUid + && controller.mAtmInternal.isUidForeground(callingUid); switch (key.type) { case ActivityManager.INTENT_SENDER_ACTIVITY: @@ -419,7 +423,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { uid, finalIntent, resolvedType, finishedReceiver, code, null, null, requiredPermission, options, (finishedReceiver != null), false, userId, - mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken)); + mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken) + || allowTrampoline); if (sent == ActivityManager.BROADCAST_SUCCESS) { sendFinish = false; } @@ -433,7 +438,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType, key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE, key.packageName, userId, - mAllowBgActivityStartsForServiceSender.contains(whitelistToken)); + mAllowBgActivityStartsForServiceSender.contains(whitelistToken) + || allowTrampoline); } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startService intent", e); } catch (TransactionTooLargeException e) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index c1be3872e031..003ddd13f152 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -145,6 +145,11 @@ public final class ProcessList { static final int CACHED_APP_MAX_ADJ = 999; static final int CACHED_APP_MIN_ADJ = 900; + // This is the oom_adj level that we allow to die first. This cannot be equal to + // CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of + // CACHED_APP_MAX_ADJ. + static final int CACHED_APP_LMK_FIRST_ADJ = 950; + // Number of levels we have available for different service connection group importance // levels. static final int CACHED_APP_IMPORTANCE_LEVELS = 5; @@ -266,7 +271,7 @@ public final class ProcessList { // can't give it a different value for every possible kind of process. private final int[] mOomAdj = new int[] { FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, - BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ + BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ }; // These are the low-end OOM level limits. This is appropriate for an // HVGA or smaller phone with less than 512MB. Values are in KB. @@ -1497,7 +1502,7 @@ public final class ProcessList { mService.mNativeDebuggingApp = null; } - if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0 + if (app.info.isCodeIntegrityPreferred() || (app.info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) { runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; @@ -1730,7 +1735,7 @@ public final class ProcessList { app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, app.info.packageName, - packageNames, visibleVolIds, + packageNames, visibleVolIds, /*useBlastulaPool=*/ false, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, @@ -2379,14 +2384,14 @@ public final class ProcessList { } @GuardedBy("mService") - void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) { + void setAllHttpProxyLocked() { for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); // Don't dispatch to isolated processes as they can't access // ConnectivityManager and don't have network privileges anyway. if (r.thread != null && !r.isolated) { try { - r.thread.setHttpProxy(host, port, exclList, pacFileUrl); + r.thread.updateHttpProxy(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index fc2a444e3267..da5ce1c45610 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -16,8 +16,10 @@ package com.android.server.am; -import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; -import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import com.android.internal.app.procstats.ServiceState; +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.LocalServices; +import com.android.server.notification.NotificationManagerInternal; import android.app.INotificationManager; import android.app.Notification; @@ -42,11 +44,6 @@ import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; - -import com.android.internal.app.procstats.ServiceState; -import com.android.internal.os.BatteryStatsImpl; -import com.android.server.LocalServices; -import com.android.server.notification.NotificationManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriPermissionOwner; @@ -55,6 +52,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + /** * A running application service. */ @@ -114,7 +114,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN long executingStart; // start time of last execute request. boolean createdFromFg; // was this service last created due to a foreground process call? int crashCount; // number of times proc has crashed with service running - int lastKnownOomAdj; // last known OOM adjustment for process (used if ProcessRecord is null) int totalRestartCount; // number of times we have had to restart. int restartCount; // number of restarts performed in a row. long restartDelay; // delay until next restart attempt. diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 27edbbf4f2d5..b71a7517ab12 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -174,10 +174,11 @@ public class AttentionManagerService extends SystemService { @Override public void onSuccess(int requestCode, int result, long timestamp) { callback.onSuccess(requestCode, result, timestamp); - userState.mAttentionCheckCache = new AttentionCheckCache( - SystemClock.uptimeMillis(), result, - timestamp); - + synchronized (mLock) { + userState.mAttentionCheckCache = new AttentionCheckCache( + SystemClock.uptimeMillis(), result, + timestamp); + } StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, result); } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java new file mode 100644 index 000000000000..d652f93ccf04 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -0,0 +1,807 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** @hide */ +/*package*/ final class AudioDeviceBroker { + + private static final String TAG = "AudioDeviceBroker"; + + private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s + + /*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000; + // Timeout for connection to bluetooth headset service + /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; + + private final @NonNull AudioService mAudioService; + private final @NonNull Context mContext; + + /** Forced device usage for communications sent to AudioSystem */ + private int mForcedUseForComm; + /** + * Externally reported force device usage state returned by getters: always consistent + * with requests by setters */ + private int mForcedUseForCommExt; + + // Manages all connected devices, only ever accessed on the message loop + //### or make it synchronized + private final AudioDeviceInventory mDeviceInventory; + // Manages notifications to BT service + private final BtHelper mBtHelper; + + + //------------------------------------------------------------------- + private static final Object sLastDeviceConnectionMsgTimeLock = new Object(); + private static long sLastDeviceConnectMsgTime = 0; + + private final Object mBluetoothA2dpEnabledLock = new Object(); + // Request to override default use of A2DP for media. + @GuardedBy("mBluetoothA2dpEnabledLock") + private boolean mBluetoothA2dpEnabled; + + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mA2dpAvrcpLock = new Object(); + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mHearingAidLock = new Object(); + + // lock always taken when accessing AudioService.mSetModeDeathHandlers + /*package*/ final Object mSetModeLock = new Object(); + + //------------------------------------------------------------------- + /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { + mContext = context; + mAudioService = service; + setupMessaging(context); + mBtHelper = new BtHelper(this); + mDeviceInventory = new AudioDeviceInventory(this); + + mForcedUseForComm = AudioSystem.FORCE_NONE; + mForcedUseForCommExt = mForcedUseForComm; + + } + + /*package*/ Context getContext() { + return mContext; + } + + //--------------------------------------------------------------------- + // Communication from AudioService + // All methods are asynchronous and never block + // All permission checks are done in AudioService, all incoming calls are considered "safe" + // All post* methods are asynchronous + + /*package*/ void onSystemReady() { + mBtHelper.onSystemReady(); + } + + /*package*/ void onAudioServerDied() { + // Restore forced usage for communications and record + onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied"); + onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied"); + // restore devices + sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); + } + + /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) { + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + useCase, config, eventSource); + } + + /*package*/ void toggleHdmiIfConnected_Async() { + sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE); + } + + /*package*/ void disconnectAllBluetoothProfiles() { + mBtHelper.disconnectAllBluetoothProfiles(); + } + + /** + * Handle BluetoothHeadset intents where the action is one of + * {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or + * {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}. + * @param intent + */ + /*package*/ void receiveBtEvent(@NonNull Intent intent) { + mBtHelper.receiveBtEvent(intent); + } + + /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { + synchronized (mBluetoothA2dpEnabledLock) { + if (mBluetoothA2dpEnabled == on) { + return; + } + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + source); + } + } + + /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) { + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + + mForcedUseForCommExt = mForcedUseForComm; + setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + } + + /*package*/ boolean isSpeakerphoneOn() { + return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + } + + /*package*/ void setWiredDeviceConnectionState(int type, + @AudioService.ConnectionState int state, String address, String name, + String caller) { + //TODO move logging here just like in setBluetooth* methods + mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + } + + /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state + // only querying address as this is the only readily available field + // on the device + + " addr=" + device.getAddress() + + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent + + " vol=" + a2dpVolume)).printLog(TAG)); + if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(device))) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP connection state ignored")); + return 0; + } + return mDeviceInventory.setBluetoothA2dpDeviceConnectionState( + device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setHearingAidDeviceConnectionState state=" + state + + " addr=" + device.getAddress() + + " supprNoisy=" + suppressNoisyIntent + + " src=" + eventSource)).printLog(TAG)); + return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice); + } + + // never called by system components + /*package*/ void setBluetoothScoOnByApp(boolean on) { + mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + } + + /*package*/ boolean isBluetoothScoOnForApp() { + return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; + } + + /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { + //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); + if (on) { + // do not accept SCO ON if SCO audio is not connected + if (!mBtHelper.isBluetoothScoOn()) { + mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; + return; + } + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + mForcedUseForCommExt = mForcedUseForComm; + AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); + // Un-mute ringtone stream volume + mAudioService.setUpdateRingerModeServiceInt(); + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + return mDeviceInventory.startWatchingRoutes(observer); + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mDeviceInventory.getCurAudioRoutes(); + } + + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.isAvrcpAbsoluteVolumeSupported(); + } + } + + /*package*/ boolean isBluetoothA2dpOn() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) { + sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index); + } + + /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) { + sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType); + } + + /*package*/ void postDisconnectBluetoothSco(int exceptPid) { + sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid); + } + + /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { + sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); + } + + /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); + } + + /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) { + mBtHelper.stopBluetoothScoForClient(cb, eventSource); + } + + //--------------------------------------------------------------------- + // Communication with (to) AudioService + //TODO check whether the AudioService methods are candidates to move here + /*package*/ void postAccessoryPlugMediaUnmute(int device) { + mAudioService.postAccessoryPlugMediaUnmute(device); + } + + /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) { + return mAudioService.getStreamState(streamType); + } + + /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() { + return mAudioService.mSetModeDeathHandlers; + } + + /*package*/ int getDeviceForStream(int streamType) { + return mAudioService.getDeviceForStream(streamType); + } + + /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) { + mAudioService.setDeviceVolume(streamState, device); + } + + /*packages*/ void observeDevicesForAllStreams() { + mAudioService.observeDevicesForAllStreams(); + } + + /*package*/ boolean isInCommunication() { + return mAudioService.isInCommunication(); + } + + /*package*/ boolean hasMediaDynamicPolicy() { + return mAudioService.hasMediaDynamicPolicy(); + } + + /*package*/ ContentResolver getContentResolver() { + return mAudioService.getContentResolver(); + } + + /*package*/ void checkMusicActive(int deviceType, String caller) { + mAudioService.checkMusicActive(deviceType, caller); + } + + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + mAudioService.checkVolumeCecOnHdmiConnection(state, caller); + } + + //--------------------------------------------------------------------- + // Message handling on behalf of helper classes + /*package*/ void broadcastScoConnectionState(int state) { + sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state); + } + + /*package*/ void broadcastBecomingNoisy() { + sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); + } + + //###TODO unify with handleSetA2dpSinkConnectionState + /*package*/ void postA2dpSinkConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + //###TODO unify with handleSetA2dpSourceConnectionState + /*package*/ void postA2dpSourceConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void postSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) { + sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay); + } + + /*package*/ void postSetHearingAidConnectionState( + @AudioService.BtProfileConnectionState int state, + @NonNull BluetoothDevice device, int delay) { + sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE, + state, + device, + delay); + } + + //--------------------------------------------------------------------- + // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory) + // only call from a "handle"* method or "on"* method + + // Handles request to override default use of A2DP for media. + //@GuardedBy("mConnectedDevices") + /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) { + // for logging only + final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).append(" src:").append(source).toString(); + + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + onSetForceUse( + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + eventSource); + } + } + + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + } + + /*package*/ void handleDisconnectA2dp() { + mDeviceInventory.disconnectA2dp(); + } + /*package*/ void handleDisconnectA2dpSink() { + mDeviceInventory.disconnectA2dpSink(); + } + + /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version? + // might be ok here because called on BT thread? + sync happening in + // checkSendBecomingNoisyIntent + final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, + AudioSystem.DEVICE_NONE); + final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo + + " state= " + state + + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock()); + } + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void handleDisconnectHearingAid() { + mDeviceInventory.disconnectHearingAid(); + } + + /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state, + btDeviceInfo); + } + + /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) { + sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay); + } + + /*package*/ void handleCancelFailureToConnectToBtHeadsetService() { + mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + } + + /*package*/ void postReportNewRoutes() { + sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP); + } + + /*package*/ void cancelA2dpDockTimeout() { + mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpDockTimeout() { + return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { + return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(btDevice)); + } + + /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { + sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); + } + + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + synchronized (mA2dpAvrcpLock) { + mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); + } + } + + /*package*/ boolean getBluetoothA2dpEnabled() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.getA2dpCodec(device); + } + } + + //--------------------------------------------------------------------- + // Internal handling of messages + // These methods are ALL synchronous, in response to message handling in BrokerHandler + // Blocking in any of those will block the message queue + + private void onSetForceUse(int useCase, int config, String eventSource) { + if (useCase == AudioSystem.FOR_MEDIA) { + postReportNewRoutes(); + } + AudioService.sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } + + private void onSendBecomingNoisyIntent() { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + } + + //--------------------------------------------------------------------- + // Message handling + private BrokerHandler mBrokerHandler; + private BrokerThread mBrokerThread; + private PowerManager.WakeLock mBrokerEventWakeLock; + + private void setupMessaging(Context ctxt) { + final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE); + mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "handleAudioDeviceEvent"); + mBrokerThread = new BrokerThread(); + mBrokerThread.start(); + waitForBrokerHandlerCreation(); + } + + private void waitForBrokerHandlerCreation() { + synchronized (this) { + while (mBrokerHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + Log.e(TAG, "Interruption while waiting on BrokerHandler"); + } + } + } + } + + /** Class that handles the device broker's message queue */ + private class BrokerThread extends Thread { + BrokerThread() { + super("AudioDeviceBroker"); + } + + @Override + public void run() { + // Set this thread up so the handler will work on it + Looper.prepare(); + + synchronized (AudioDeviceBroker.this) { + mBrokerHandler = new BrokerHandler(); + + // Notify that the handler has been created + AudioDeviceBroker.this.notify(); + } + + Looper.loop(); + } + } + + /** Class that handles the message queue */ + private class BrokerHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_RESTORE_DEVICES: + mDeviceInventory.onRestoreDevices(); + synchronized (mBluetoothA2dpEnabledLock) { + mBtHelper.onAudioServerDiedRestoreA2dp(); + } + break; + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + mDeviceInventory.onSetWiredDeviceConnectionState( + (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj); + break; + case MSG_I_BROADCAST_BT_CONNECTION_STATE: + mBtHelper.onBroadcastScoConnectionState(msg.arg1); + break; + case MSG_IIL_SET_FORCE_USE: // intented fall-through + case MSG_IIL_SET_FORCE_BT_A2DP_USE: + onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj); + break; + case MSG_REPORT_NEW_ROUTES: + mDeviceInventory.onReportNewRoutes(); + break; + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSinkConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2); + break; + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSourceConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + break; + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + mDeviceInventory.onSetHearingAidConnectionState( + (BluetoothDevice) msg.obj, msg.arg1); + break; + case MSG_BT_HEADSET_CNCT_FAILED: + mBtHelper.resetBluetoothSco(); + break; + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + // msg.obj == address of BTA2DP device + mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); + break; + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + final int a2dpCodec; + final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + synchronized (mA2dpAvrcpLock) { + a2dpCodec = mBtHelper.getA2dpCodec(btDevice); + } + mDeviceInventory.onBluetoothA2dpDeviceConfigChange( + new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec)); + break; + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; + case MSG_II_SET_HEARING_AID_VOLUME: + mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2); + break; + case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME: + mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); + break; + case MSG_I_DISCONNECT_BT_SCO: + mBtHelper.disconnectBluetoothSco(msg.arg1); + break; + case MSG_TOGGLE_HDMI: + mDeviceInventory.onToggleHdmi(); + break; + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + mDeviceInventory.onBluetoothA2dpActiveDeviceChange( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj); + break; + default: + Log.wtf(TAG, "Invalid message " + msg.what); + } + if (isMessageHandledUnderWakelock(msg.what)) { + try { + mBrokerEventWakeLock.release(); + } catch (Exception e) { + Log.e(TAG, "Exception releasing wakelock", e); + } + } + } + } + + // List of all messages. If a message has be handled under wakelock, add it to + // the isMessageHandledUnderWakelock(int) method + // Naming of msg indicates arguments, using JNI argument grammar + // (e.g. II indicates two int args, IL indicates int and Obj arg) + private static final int MSG_RESTORE_DEVICES = 1; + private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2; + private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; + private static final int MSG_IIL_SET_FORCE_USE = 4; + private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; + private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6; + private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; + private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; + private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; + private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10; + private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; + private static final int MSG_REPORT_NEW_ROUTES = 13; + private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; + private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; + private static final int MSG_I_DISCONNECT_BT_SCO = 16; + private static final int MSG_TOGGLE_HDMI = 17; + private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; + + + private static boolean isMessageHandledUnderWakelock(int msgId) { + switch(msgId) { + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_TOGGLE_HDMI: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + return true; + default: + return false; + } + } + + // Message helper methods + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private void sendMsg(int msg, int existingMsgPolicy, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay); + } + + private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay); + } + + private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay); + } + + private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay); + } + + private void sendMsgNoDelay(int msg, int existingMsgPolicy) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0); + } + + private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0); + } + + private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0); + } + + private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0); + } + + private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0); + } + + private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0); + } + + private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, + int delay) { + if (existingMsgPolicy == SENDMSG_REPLACE) { + mBrokerHandler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) { + return; + } + + if (isMessageHandledUnderWakelock(msg)) { + final long identity = Binder.clearCallingIdentity(); + try { + mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS); + } catch (Exception e) { + Log.e(TAG, "Exception acquiring wakelock", e); + } + Binder.restoreCallingIdentity(identity); + } + + synchronized (sLastDeviceConnectionMsgTimeLock) { + long time = SystemClock.uptimeMillis() + delay; + + switch (msg) { + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + if (sLastDeviceConnectMsgTime >= time) { + // add a little delay to make sure messages are ordered as expected + time = sLastDeviceConnectMsgTime + 30; + } + sLastDeviceConnectMsgTime = time; + break; + default: + break; + } + + mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj), + time); + } + } + + //------------------------------------------------------------- + // internal utilities + private void sendBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java new file mode 100644 index 000000000000..eb76e6e02edc --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -0,0 +1,1024 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioDevicePort; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPort; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** + * Class to manage the inventory of all connected devices. + * This class is thread-safe. + */ +public final class AudioDeviceInventory { + + private static final String TAG = "AS.AudioDeviceInventory"; + + // Actual list of connected devices + // Key for map created from DeviceInfo.makeDeviceListKey() + private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>(); + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // cache of the address of the last dock the device was connected to + private String mDockAddress; + + // Monitoring of audio routes. Protected by mAudioRoutes. + final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); + final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = + new RemoteCallbackList<IAudioRoutesObserver>(); + + //------------------------------------------------------------ + /** + * Class to store info about connected devices. + * Use makeDeviceListKey() to make a unique key for this list. + */ + private static class DeviceInfo { + final int mDeviceType; + final String mDeviceName; + final String mDeviceAddress; + int mDeviceCodecFormat; + + DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { + mDeviceType = deviceType; + mDeviceName = deviceName; + mDeviceAddress = deviceAddress; + mDeviceCodecFormat = deviceCodecFormat; + } + + @Override + public String toString() { + return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) + + " name:" + mDeviceName + + " addr:" + mDeviceAddress + + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; + } + + /** + * Generate a unique key for the mConnectedDevices List by composing the device "type" + * and the "address" associated with a specific instance of that device type + */ + private static String makeDeviceListKey(int device, String deviceAddress) { + return "0x" + Integer.toHexString(device) + ":" + deviceAddress; + } + } + + /** + * A class just for packaging up a set of connection parameters. + */ + /*package*/ class WiredDeviceConnectionState { + public final int mType; + public final @AudioService.ConnectionState int mState; + public final String mAddress; + public final String mName; + public final String mCaller; + + /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + mType = type; + mState = state; + mAddress = address; + mName = name; + mCaller = caller; + } + } + + //------------------------------------------------------------ + // Message handling from AudioDeviceBroker + + /** + * Restore previously connected devices. Use in case of audio server crash + * (see AudioService.onAudioServerDied() method) + */ + /*package*/ void onRestoreDevices() { + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceInfo di = mConnectedDevices.valueAt(i); + AudioSystem.setDeviceConnectionState( + di.mDeviceType, + AudioSystem.DEVICE_STATE_AVAILABLE, + di.mDeviceAddress, + di.mDeviceName, + di.mDeviceCodecFormat); + } + } + } + + /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, + @AudioService.BtProfileConnectionState int state, int a2dpVolume) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state=" + + state + " is dock=" + btDevice.isBluetoothDock()); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP sink connected: device addr=" + address + " state=" + state)); + + final int a2dpCodec; + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + a2dpCodec = btInfo.getCodec(); + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address, + AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + mDeviceBroker.cancelA2dpDockTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) { + mDeviceBroker.cancelA2dpDockTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress, + AudioSystem.AUDIO_FORMAT_DEFAULT); + } + } + if (a2dpVolume != -1) { + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onSetA2dpSinkConnectionState"); + mDeviceBroker.setDeviceVolume( + streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + makeA2dpDeviceAvailable(address, btDevice.getName(), + "onSetA2dpSinkConnectionState", a2dpCodec); + } + } + } + + /*package*/ void onSetA2dpSourceConnectionState( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + + state); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcAvailable(address); + } + } + } + + /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice, + @AudioService.BtProfileConnectionState int state) { + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onSetHearingAidConnectionState addr=" + address)); + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceAvailable(address, btDevice.getName(), + "onSetHearingAidConnectionState"); + } + } + } + + /*package*/ void onBluetoothA2dpDeviceConfigChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpDeviceConfigChange addr=" + address)); + + final int a2dpCodec = btInfo.getCodec(); + + synchronized (mConnectedDevices) { + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); + return; + } + // Device is connected + if (di.mDeviceCodecFormat != a2dpCodec) { + di.mDeviceCodecFormat = a2dpCodec; + mConnectedDevices.replace(key, di); + } + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" + + di.mDeviceCodecFormat); + } + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) { + // force A2DP device disconnection in case of error so that AudioService state + // is consistent with audio policy manager state + final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onBluetoothA2dpActiveDeviceChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + int a2dpVolume = btInfo.getVolume(); + final int a2dpCodec = btInfo.getCodec(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpActiveDeviceChange addr=" + address)); + + synchronized (mConnectedDevices) { + //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo + // for this type of message + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + return; + } + + // Device is connected + if (a2dpVolume != -1) { + final AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onBluetoothA2dpActiveDeviceChange"); + mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { + int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + // force A2DP device disconnection in case of error so that AudioService state is + // consistent with audio policy manager state + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow(address, a2dpCodec); + } + } + + /*package*/ void onReportNewRoutes() { + int n = mRoutesObservers.beginBroadcast(); + if (n > 0) { + AudioRoutesInfo routes; + synchronized (mCurAudioRoutes) { + routes = new AudioRoutesInfo(mCurAudioRoutes); + } + while (n > 0) { + n--; + IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); + try { + obs.dispatchAudioRoutesChanged(routes); + } catch (RemoteException e) { } + } + } + mRoutesObservers.finishBroadcast(); + mDeviceBroker.observeDevicesForAllStreams(); + } + + private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB; + + /*package*/ void onSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState wdcs) { + AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); + + synchronized (mConnectedDevices) { + if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) + && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { + mDeviceBroker.setBluetoothA2dpOnInt(true, + "onSetWiredDeviceConnectionState state DISCONNECTED"); + } + + if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress, + wdcs.mName)) { + // change of connection state failed, bailout + return; + } + if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { + if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { + mDeviceBroker.setBluetoothA2dpOnInt(false, + "onSetWiredDeviceConnectionState state not DISCONNECTED"); + } + mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller); + } + mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); + sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName); + updateAudioRoutes(wdcs.mType, wdcs.mState); + } + } + + /*package*/ void onToggleHdmi() { + synchronized (mConnectedDevices) { + // Is HDMI connected? + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); + return; + } + // Toggle HDMI to retrigger broadcast with proper formats. + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", + "android"); // disconnect + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_AVAILABLE, "", "", + "android"); // reconnect + } + } + //------------------------------------------------------------ + // + + /** + * Implements the communication with AudioSystem to (dis)connect a device in the native layers + * @param connect true if connection + * @param device the device type + * @param address the address of the device + * @param deviceName human-readable name of device + * @return false if an error was reported by AudioSystem + */ + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + + Integer.toHexString(device) + " address:" + address + + " name:" + deviceName + ")"); + } + synchronized (mConnectedDevices) { + final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceKey:" + deviceKey); + } + DeviceInfo di = mConnectedDevices.get(deviceKey); + boolean isConnected = di != null; + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); + } + if (connect && !isConnected) { + final int res = AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + + " due to command error " + res); + return false; + } + mConnectedDevices.put(deviceKey, new DeviceInfo( + device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(device); + return true; + } else if (!connect && isConnected) { + AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + // always remove even if disconnection failed + mConnectedDevices.remove(deviceKey); + return true; + } + Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + + ", deviceSpec=" + di + ", connect=" + connect); + } + return false; + } + + + /*package*/ void disconnectA2dp() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + makeA2dpDeviceUnavailableLater(deviceAddress, delay) + ); + } + } + } + } + + /*package*/ void disconnectA2dpSink() { + synchronized (mConnectedDevices) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); + } + } + + /*package*/ void disconnectHearingAid() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mHearingAidLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_HEARING_AID devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + // TODO delay not used? + makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) + ); + } + } + } + } + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { + synchronized (mConnectedDevices) { + return checkSendBecomingNoisyIntentInt(device, state, musicDevice); + } + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + synchronized (mCurAudioRoutes) { + AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); + mRoutesObservers.register(observer); + return routes; + } + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mCurAudioRoutes; + } + + /*package*/ int setBluetoothA2dpDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { + int delay; + if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { + throw new IllegalArgumentException("invalid profile " + profile); + } + synchronized (mConnectedDevices) { + if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { + int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + intState, musicDevice); + } else { + delay = 0; + } + + final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device + + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec + + " suppressNoisyIntent: " + suppressNoisyIntent); + } + + final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo = + new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec); + if (profile == BluetoothProfile.A2DP) { + mDeviceBroker.postA2dpSinkConnection(state, + a2dpDeviceInfo, + delay); + } else { //profile == BluetoothProfile.A2DP_SINK + mDeviceBroker.postA2dpSourceConnection(state, + a2dpDeviceInfo, + delay); + } + } + return delay; + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + return setBluetoothA2dpDeviceConnectionState(device, state, profile, + suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + // state == BluetoothProfile.STATE_CONNECTED + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i); + if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + continue; + } + // If A2DP device exists, this is either an active device change or + // device config change + final String existingDevicekey = mConnectedDevices.keyAt(i); + final String deviceName = device.getName(); + final String address = device.getAddress(); + final String newDeviceKey = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + // Device not equal to existing device, active device change + if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { + mConnectedDevices.remove(existingDevicekey); + mConnectedDevices.put(newDeviceKey, new DeviceInfo( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, + address, a2dpCodec)); + mDeviceBroker.postA2dpActiveDeviceChange( + new BtHelper.BluetoothA2dpDeviceInfo( + device, a2dpVolume, a2dpCodec)); + return 0; + } else { + // Device config change for existing device + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); + return 0; + } + } + } + return 0; + } + + /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + synchronized (mConnectedDevices) { + int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); + mDeviceBroker.postSetWiredDeviceConnectionState( + new WiredDeviceConnectionState(type, state, address, name, caller), + delay); + return delay; + } + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { + int delay; + synchronized (mConnectedDevices) { + if (!suppressNoisyIntent) { + int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID, + intState, musicDevice); + } else { + delay = 0; + } + mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); + return delay; + } + } + + + //------------------------------------------------------------------- + // Internal utilities + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceAvailable(String address, String name, String eventSource, + int a2dpCodec) { + // enable A2DP before notifying A2DP connection to avoid unnecessary processing in + // audio policy manager + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); + // Reset A2DP suspend state each time a new sink is connected + AudioSystem.setParameters("A2dpSuspended=false"); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, + address, a2dpCodec)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + if (address == null) { + return; + } + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + // Remove A2DP routes as well + setCurrentAudioRouteNameIfPossible(null); + if (mDockAddress == address) { + mDockAddress = null; + } + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { + // prevent any activity on the A2DP audio output to avoid unwanted + // reconnection of the sink. + AudioSystem.setParameters("A2dpSuspended=true"); + // retrieve DeviceInfo before removing device + final String deviceKey = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); + final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : + AudioSystem.AUDIO_FORMAT_DEFAULT; + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(deviceKey); + // send the delayed message to make the device unavailable later + mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs); + } + + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcAvailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { + final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC) + .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC); + + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.setDeviceVolume( + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC), + AudioSystem.DEVICE_OUT_HEARING_AID); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); + // Remove Hearing Aid routes as well + setCurrentAudioRouteNameIfPossible(null); + } + + @GuardedBy("mConnectedDevices") + private void setCurrentAudioRouteNameIfPossible(String name) { + synchronized (mCurAudioRoutes) { + if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { + return; + } + if (name != null || !isCurrentDeviceConnected()) { + mCurAudioRoutes.bluetoothName = name; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + @GuardedBy("mConnectedDevices") + private boolean isCurrentDeviceConnected() { + return mConnectedDevices.values().stream().anyMatch(deviceInfo -> + TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); + } + + // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only + // sent if: + // - none of these devices are connected anymore after one is disconnected AND + // - the device being disconnected is actually used for music. + // Access synchronized on mConnectedDevices + private int mBecomingNoisyIntentDevices = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI + | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE + | AudioSystem.DEVICE_OUT_HEARING_AID; + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + @GuardedBy("mConnectedDevices") + private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) { + if (state != 0) { + return 0; + } + if ((device & mBecomingNoisyIntentDevices) == 0) { + return 0; + } + int delay = 0; + int devices = 0; + for (int i = 0; i < mConnectedDevices.size(); i++) { + int dev = mConnectedDevices.valueAt(i).mDeviceType; + if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) + && ((dev & mBecomingNoisyIntentDevices) != 0)) { + devices |= dev; + } + } + if (musicDevice == AudioSystem.DEVICE_NONE) { + musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + } + // ignore condition on device being actually used for music when in communication + // because music routing is altered in this case. + // also checks whether media routing if affected by a dynamic policy + if (((device == musicDevice) || mDeviceBroker.isInCommunication()) + && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) { + mDeviceBroker.broadcastBecomingNoisy(); + delay = 1000; + } + + return delay; + } + + // Intent "extra" data keys. + private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; + private static final String CONNECT_INTENT_KEY_STATE = "state"; + private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; + private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; + private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; + private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; + private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; + + private void sendDeviceConnectionIntent(int device, int state, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + + " state:0x" + Integer.toHexString(state) + " address:" + address + + " name:" + deviceName + ");"); + } + Intent intent = new Intent(); + + switch(device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 0); + break; + case AudioSystem.DEVICE_OUT_USB_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", + AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); + break; + case AudioSystem.DEVICE_IN_USB_HEADSET: + if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE) { + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + } else { + // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing + return; + } + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + configureHdmiPlugIntent(intent, state); + break; + } + + if (intent.getAction() == null) { + return; + } + + intent.putExtra(CONNECT_INTENT_KEY_STATE, state); + intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); + intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); + + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateAudioRoutes(int device, int state) { + int connType = 0; + + switch (device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + connType = AudioRoutesInfo.MAIN_HEADSET; + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + connType = AudioRoutesInfo.MAIN_HEADPHONES; + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + connType = AudioRoutesInfo.MAIN_HDMI; + break; + case AudioSystem.DEVICE_OUT_USB_DEVICE: + case AudioSystem.DEVICE_OUT_USB_HEADSET: + connType = AudioRoutesInfo.MAIN_USB; + break; + } + + synchronized (mCurAudioRoutes) { + if (connType == 0) { + return; + } + int newConn = mCurAudioRoutes.mainType; + if (state != 0) { + newConn |= connType; + } else { + newConn &= ~connType; + } + if (newConn != mCurAudioRoutes.mainType) { + mCurAudioRoutes.mainType = newConn; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { + intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); + intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); + if (state != AudioService.CONNECTION_STATE_CONNECTED) { + return; + } + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + int[] portGeneration = new int[1]; + int status = AudioSystem.listAudioPorts(ports, portGeneration); + if (status != AudioManager.SUCCESS) { + Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); + return; + } + for (AudioPort port : ports) { + if (!(port instanceof AudioDevicePort)) { + continue; + } + final AudioDevicePort devicePort = (AudioDevicePort) port; + if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI + && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) { + continue; + } + // found an HDMI port: format the list of supported encodings + int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); + if (formats.length > 0) { + ArrayList<Integer> encodingList = new ArrayList(1); + for (int format : formats) { + // a format in the list can be 0, skip it + if (format != AudioFormat.ENCODING_INVALID) { + encodingList.add(format); + } + } + final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); + intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); + } + // find the maximum supported number of channels + int maxChannels = 0; + for (int mask : devicePort.channelMasks()) { + int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); + if (channelCount > maxChannels) { + maxChannels = channelCount; + } + } + intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index de389bc3aa01..df33bf249133 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -35,14 +36,9 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IUidObserver; import android.app.NotificationManager; -import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothCodecConfig; -import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -65,14 +61,12 @@ import android.hardware.hdmi.HdmiPlaybackClient; import android.hardware.hdmi.HdmiTvClient; import android.hardware.usb.UsbManager; import android.media.AudioAttributes; -import android.media.AudioDevicePort; import android.media.AudioFocusInfo; import android.media.AudioFocusRequest; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPlaybackConfiguration; -import android.media.AudioPort; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; @@ -104,7 +98,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -120,8 +113,6 @@ import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; @@ -137,10 +128,8 @@ import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.audio.AudioServiceEvents.ForceUseEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; -import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent; import com.android.server.pm.UserManagerService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -150,6 +139,8 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; @@ -175,19 +166,20 @@ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, AccessibilityManager.AccessibilityServicesStateChangeListener { - private static final String TAG = "AudioService"; + private static final String TAG = "AS.AudioService"; /** Debug audio mode */ - protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG); + protected static final boolean DEBUG_MODE = false; /** Debug audio policy feature */ - protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG); + protected static final boolean DEBUG_AP = false; /** Debug volumes */ - protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG); + protected static final boolean DEBUG_VOL = false; /** debug calls to devices APIs */ - protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG); + protected static final boolean DEBUG_DEVICES = false; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -213,11 +205,11 @@ public class AudioService extends IAudioService.Stub return mPlatformType == AudioSystem.PLATFORM_VOICE; } - private boolean isPlatformTelevision() { + /*package*/ boolean isPlatformTelevision() { return mPlatformType == AudioSystem.PLATFORM_TELEVISION; } - private boolean isPlatformAutomotive() { + /*package*/ boolean isPlatformAutomotive() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } @@ -242,52 +234,40 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_FORCE_USE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; private static final int MSG_SET_ALL_VOLUMES = 10; - private static final int MSG_REPORT_NEW_ROUTES = 12; - private static final int MSG_SET_FORCE_BT_A2DP_USE = 13; - private static final int MSG_CHECK_MUSIC_ACTIVE = 14; - private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18; - private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19; - private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; - private static final int MSG_SYSTEM_READY = 21; - private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; - private static final int MSG_UNMUTE_STREAM = 24; - private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25; - private static final int MSG_INDICATE_SYSTEM_READY = 26; - private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27; - private static final int MSG_NOTIFY_VOL_EVENT = 28; - private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29; - private static final int MSG_ENABLE_SURROUND_FORMATS = 30; + private static final int MSG_CHECK_MUSIC_ACTIVE = 11; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13; + private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14; + private static final int MSG_UNLOAD_SOUND_EFFECTS = 15; + private static final int MSG_SYSTEM_READY = 16; + private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17; + private static final int MSG_UNMUTE_STREAM = 18; + private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19; + private static final int MSG_INDICATE_SYSTEM_READY = 20; + private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21; + private static final int MSG_NOTIFY_VOL_EVENT = 22; + private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23; + private static final int MSG_ENABLE_SURROUND_FORMATS = 24; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100; - private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101; - private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102; - private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103; - private static final int MSG_DISABLE_AUDIO_FOR_UID = 104; - private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105; - private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106; - private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107; + private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; // end of messages handled under wakelock - private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; - // Timeout for connection to bluetooth headset service - private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; - // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; - private static final int BT_HEARING_AID_GAIN_MIN = -128; - /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ private AudioHandler mAudioHandler; /** @see VolumeStreamState */ private VolumeStreamState[] mStreamStates; + + /*package*/ VolumeStreamState getStreamState(int stream) { + return mStreamStates[stream]; + } + private SettingsObserver mSettingsObserver; private int mMode = AudioSystem.MODE_NORMAL; @@ -477,135 +457,13 @@ public class AudioService extends IAudioService.Stub private final UserRestrictionsListener mUserRestrictionsListener = new AudioServiceUserRestrictionsListener(); - // Devices currently connected - // Use makeDeviceListKey() to make a unique key for this list. - private class DeviceListSpec { - int mDeviceType; - String mDeviceName; - String mDeviceAddress; - int mDeviceCodecFormat; - - DeviceListSpec(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat) { - mDeviceType = deviceType; - mDeviceName = deviceName; - mDeviceAddress = deviceAddress; - mDeviceCodecFormat = deviceCodecFormat; - } - - public String toString() { - return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName - + " address:" + mDeviceAddress - + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; - } - } - - // Generate a unique key for the mConnectedDevices List by composing the device "type" - // and the "address" associated with a specific instance of that device type - private String makeDeviceListKey(int device, String deviceAddress) { - return "0x" + Integer.toHexString(device) + ":" + deviceAddress; - } - - private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>(); - - private class BluetoothA2dpDeviceInfo { - BluetoothDevice mBtDevice; - int mVolume; - int mCodec; - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) { - this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); - } - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice, - int volume, int codec) { - mBtDevice = btDevice; - mVolume = volume; - mCodec = codec; - } - - public BluetoothDevice getBtDevice() { - return mBtDevice; - } - - public int getVolume() { - return mVolume; - } - - public int getCodec() { - return mCodec; - } - } - - private int mapBluetoothCodecToAudioFormat(int btCodecType) { - switch (btCodecType) { - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: - return AudioSystem.AUDIO_FORMAT_SBC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: - return AudioSystem.AUDIO_FORMAT_AAC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: - return AudioSystem.AUDIO_FORMAT_APTX; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: - return AudioSystem.AUDIO_FORMAT_APTX_HD; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: - return AudioSystem.AUDIO_FORMAT_LDAC; - default: - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - } - - // Forced device usage for communications - private int mForcedUseForComm; - private int mForcedUseForCommExt; // External state returned by getters: always consistent - // with requests by setters - // List of binder death handlers for setMode() client processes. // The last process to have called setMode() is at the top of the list. - private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); - - // List of clients having issued a SCO start request - private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); - - // BluetoothHeadset API to control SCO connection - private BluetoothHeadset mBluetoothHeadset; - - // Bluetooth headset device - private BluetoothDevice mBluetoothHeadsetDevice; - - // Indicate if SCO audio connection is currently active and if the initiator is - // audio service (internal) or bluetooth headset (external) - private int mScoAudioState; - // SCO audio state is not active - private static final int SCO_STATE_INACTIVE = 0; - // SCO audio activation request waiting for headset service to connect - private static final int SCO_STATE_ACTIVATE_REQ = 1; - // SCO audio state is active or starting due to a request from AudioManager API - private static final int SCO_STATE_ACTIVE_INTERNAL = 3; - // SCO audio deactivation request waiting for headset service to connect - private static final int SCO_STATE_DEACTIVATE_REQ = 4; - // SCO audio deactivation in progress, waiting for Bluetooth audio intent - private static final int SCO_STATE_DEACTIVATING = 5; - - // SCO audio state is active due to an action in BT handsfree (either voice recognition or - // in call audio) - private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; - - // Indicates the mode used for SCO audio connection. The mode is virtual call if the request - // originated from an app targeting an API version before JB MR2 and raw audio after that. - private int mScoAudioMode; - // SCO audio mode is undefined - private static final int SCO_MODE_UNDEFINED = -1; - // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) - private static final int SCO_MODE_VIRTUAL_CALL = 0; - // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) - private static final int SCO_MODE_RAW = 1; - // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) - private static final int SCO_MODE_VR = 2; - - private static final int SCO_MODE_MAX = 2; - - // Current connection state indicated by bluetooth headset - private int mScoConnectionState; + // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers + //TODO candidate to be moved to separate class that handles synchronization + @GuardedBy("mDeviceBroker.mSetModeLock") + /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers = + new ArrayList<SetModeDeathHandler>(); // true if boot sequence has been completed private boolean mSystemReady; @@ -636,15 +494,6 @@ public class AudioService extends IAudioService.Stub // Used to play ringtones outside system_server private volatile IRingtonePlayer mRingtonePlayer; - // Request to override default use of A2DP for media. - private boolean mBluetoothA2dpEnabled; - private final Object mBluetoothA2dpEnabledLock = new Object(); - - // Monitoring of audio routes. Protected by mCurAudioRoutes. - final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); - final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers - = new RemoteCallbackList<IAudioRoutesObserver>(); - // Devices for which the volume is fixed and VolumePanel slider should be disabled int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | @@ -669,17 +518,6 @@ public class AudioService extends IAudioService.Stub private final MediaFocusControl mMediaFocusControl; - // Reference to BluetoothA2dp to query for volume. - private BluetoothHearingAid mHearingAid; - // lock always taken synchronized on mConnectedDevices - private final Object mHearingAidLock = new Object(); - // Reference to BluetoothA2dp to query for AbsoluteVolume. - private BluetoothA2dp mA2dp; - // lock always taken synchronized on mConnectedDevices - private final Object mA2dpAvrcpLock = new Object(); - // If absolute volume is supported in AVRCP device - private boolean mAvrcpAbsVolSupported = false; - // Pre-scale for Bluetooth Absolute Volume private float[] mPrescaleAbsoluteVolume = new float[] { 0.5f, // Pre-scale for index 1 @@ -687,8 +525,6 @@ public class AudioService extends IAudioService.Stub 0.85f, // Pre-scale for index 3 }; - private static Long mLastDeviceConnectMsgTime = new Long(0); - private NotificationManager mNm; private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; @@ -705,15 +541,6 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private int mAssistantUid; - // Intent "extra" data keys. - public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; - public static final String CONNECT_INTENT_KEY_STATE = "state"; - public static final String CONNECT_INTENT_KEY_ADDRESS = "address"; - public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; - public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; - public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; - public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; - // Defines the format for the connection "address" for ALSA devices public static String makeAlsaAddressString(int card, int device) { return "card=" + card + ";device=" + device + ";"; @@ -858,8 +685,6 @@ public class AudioService extends IAudioService.Stub sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); - mForcedUseForComm = AudioSystem.FORCE_NONE; - createAudioSystemThread(); AudioSystem.setErrorCallback(mAudioSystemCallback); @@ -886,6 +711,8 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + mDeviceBroker = new AudioDeviceBroker(mContext, this); + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] // array initialized by updateStreamVolumeAlias() updateStreamVolumeAlias(false /*updateVolumes*/, TAG); @@ -988,23 +815,7 @@ public class AudioService extends IAudioService.Stub sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); - mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR; - resetBluetoothSco(); - getBluetoothHeadset(); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - sendStickyBroadcastToAll(newIntent); - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.A2DP); - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEARING_AID); - } + mDeviceBroker.onSystemReady(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { synchronized (mHdmiClientLock) { @@ -1065,39 +876,22 @@ public class AudioService extends IAudioService.Stub readAndSetLowRamDevice(); - // Restore device connection states - synchronized (mConnectedDevices) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec spec = mConnectedDevices.valueAt(i); - AudioSystem.setDeviceConnectionState( - spec.mDeviceType, - AudioSystem.DEVICE_STATE_AVAILABLE, - spec.mDeviceAddress, - spec.mDeviceName, - spec.mDeviceCodecFormat); - } - } + // Restore device connection states, BT state + mDeviceBroker.onAudioServerDied(); + // Restore call state if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) { mModeLogger.log(new AudioEventLogger.StringEvent( "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")")); } - // Restore forced usage for communications and record - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm); - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm); final int forSys; synchronized (mSettingsLock) { forSys = mCameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE; } - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys); + + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied"); // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1120,20 +914,10 @@ public class AudioService extends IAudioService.Stub RotationHelper.updateOrientation(); } - synchronized (mBluetoothA2dpEnabledLock) { - final int forMed = mBluetoothA2dpEnabled ? - AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed); - } - synchronized (mSettingsLock) { final int forDock = mDockAudioMediaEnabled ? AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); updateAssistantUId(true); @@ -1209,6 +993,45 @@ public class AudioService extends IAudioService.Stub } } + /** + * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected. + */ + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + if (state != 0) { + // DEVICE_OUT_HDMI is now connected + if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + + if (isPlatformTelevision()) { + mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; + checkAllFixedVolumeDevices(); + synchronized (mHdmiClientLock) { + if (mHdmiManager != null && mHdmiPlaybackClient != null) { + mHdmiCecSink = false; + mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); + } + } + } + sendEnabledSurroundFormats(mContentResolver, true); + } else { + // DEVICE_OUT_HDMI disconnected + if (isPlatformTelevision()) { + synchronized (mHdmiClientLock) { + if (mHdmiManager != null) { + mHdmiCecSink = false; + } + } + } + } + } + private void checkAllFixedVolumeDevices() { int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1373,7 +1196,7 @@ public class AudioService extends IAudioService.Stub private void sendEncodedSurroundMode(ContentResolver cr, String eventSource) { - int encodedSurroundMode = Settings.Global.getInt( + final int encodedSurroundMode = Settings.Global.getInt( cr, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); sendEncodedSurroundMode(encodedSurroundMode, eventSource); @@ -1402,13 +1225,8 @@ public class AudioService extends IAudioService.Stub break; } if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) { - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_ENCODED_SURROUND, - forceSetting, - eventSource, - 0); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting, + eventSource); } } @@ -1632,7 +1450,7 @@ public class AudioService extends IAudioService.Stub + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream + ", userSelect=" + mUserSelectedVolumeControlStream); - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append("/").append(caller).append(" uid:").append(uid).toString())); final int streamType; @@ -1690,7 +1508,7 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -1871,16 +1689,18 @@ public class AudioService extends IAudioService.Stub if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(newIndex / 10); - } + if (DEBUG_VOL) { + Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + + newIndex + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex); } // Check if volume update should be send to Hearing Aid if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(newIndex, streamType); + Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); } // Check if volume update should be sent to Hdmi system audio. @@ -2052,7 +1872,7 @@ public class AudioService extends IAudioService.Stub + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -2127,18 +1947,20 @@ public class AudioService extends IAudioService.Stub index = rescaleIndex(index * 10, streamType, streamTypeAlias); - if (streamTypeAlias == AudioSystem.STREAM_MUSIC && - (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(index / 10); - } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC + && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 + && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index + + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(index, streamType); + Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { @@ -2881,6 +2703,10 @@ public class AudioService extends IAudioService.Stub } } + /*package*/ void setUpdateRingerModeServiceInt() { + setRingerModeInt(getRingerModeInternal(), false); + } + /** @see AudioManager#shouldVibrate(int) */ public boolean shouldVibrate(int vibrateType) { if (!mHasVibrator) return false; @@ -2921,7 +2747,7 @@ public class AudioService extends IAudioService.Stub } - private class SetModeDeathHandler implements IBinder.DeathRecipient { + /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mPid; private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client @@ -2934,7 +2760,7 @@ public class AudioService extends IAudioService.Stub public void binderDied() { int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); @@ -2949,9 +2775,7 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - final long ident = Binder.clearCallingIdentity(); - disconnectBluetoothSco(newModeOwnerPid); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } @@ -2994,7 +2818,7 @@ public class AudioService extends IAudioService.Stub int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } @@ -3006,11 +2830,11 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - disconnectBluetoothSco(newModeOwnerPid); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } - // must be called synchronized on mSetModeDeathHandlers + // must be called synchronized on mSetModeLock // setModeInt() returns a valid PID if the audio mode was successfully set to // any mode other than NORMAL. private int setModeInt(int mode, IBinder cb, int pid, String caller) { @@ -3380,26 +3204,12 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - if (on) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, - eventSource, 0); - } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){ - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - - mForcedUseForCommExt = mForcedUseForComm; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); + mDeviceBroker.setSpeakerphoneOn(on, eventSource); } /** @see AudioManager#isSpeakerphoneOn() */ public boolean isSpeakerphoneOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + return mDeviceBroker.isSpeakerphoneOn(); } /** @see AudioManager#setBluetoothScoOn(boolean) */ @@ -3410,7 +3220,7 @@ public class AudioService extends IAudioService.Stub // Only enable calls from system components if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { - mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + mDeviceBroker.setBluetoothScoOnByApp(on); return; } @@ -3418,95 +3228,57 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - setBluetoothScoOnInt(on, eventSource); - } - - public void setBluetoothScoOnInt(boolean on, String eventSource) { - Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); - if (on) { - // do not accept SCO ON if SCO audio is not connected - synchronized (mScoClients) { - if ((mBluetoothHeadset != null) - && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { - mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; - Log.w(TAG, "setBluetoothScoOnInt(true) failed because " - + mBluetoothHeadsetDevice + " is not in audio connected mode"); - return; - } - } - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - mForcedUseForCommExt = mForcedUseForComm; - AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off")); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0); - // Un-mute ringtone stream volume - setRingerModeInt(getRingerModeInternal(), false); + + mDeviceBroker.setBluetoothScoOn(on, eventSource); } - /** @see AudioManager#isBluetoothScoOn() */ + /** @see AudioManager#isBluetoothScoOn() + * Note that it doesn't report internal state, but state seen by apps (which may have + * called setBluetoothScoOn() */ public boolean isBluetoothScoOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO); + return mDeviceBroker.isBluetoothScoOnForApp(); } + // TODO investigate internal users due to deprecation of SDK API /** @see AudioManager#setBluetoothA2dpOn(boolean) */ public void setBluetoothA2dpOn(boolean on) { // for logging only final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - synchronized (mBluetoothA2dpEnabledLock) { - if (mBluetoothA2dpEnabled == on) { - return; - } - mBluetoothA2dpEnabled = on; - sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource, 0); - } + mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource); } /** @see AudioManager#isBluetoothA2dpOn() */ public boolean isBluetoothA2dpOn() { - synchronized (mBluetoothA2dpEnabledLock) { - return mBluetoothA2dpEnabled; - } + return mDeviceBroker.isBluetoothA2dpOn(); } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb, int targetSdkVersion) { - int scoAudioMode = + final int scoAudioMode = (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ? - SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED; - startBluetoothScoInt(cb, scoAudioMode); + BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED; + final String eventSource = new StringBuilder("startBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, scoAudioMode, eventSource); } /** @see AudioManager#startBluetoothScoVirtualCall() */ public void startBluetoothScoVirtualCall(IBinder cb) { - startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL); + final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int scoAudioMode){ + void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { if (!checkAudioSettingsPermission("startBluetoothSco()") || !mSystemReady) { return; } - ScoClient client = getScoClient(cb, true); - // The calling identity must be cleared before calling ScoClient.incCount(). - // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - // The caller identity must be cleared after getScoClient() because it is needed if a new - // client is created. - final long ident = Binder.clearCallingIdentity(); - client.incCount(scoAudioMode); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); } /** @see AudioManager#stopBluetoothSco() */ @@ -3515,648 +3287,15 @@ public class AudioService extends IAudioService.Stub !mSystemReady) { return; } - ScoClient client = getScoClient(cb, false); - // The calling identity must be cleared before calling ScoClient.decCount(). - // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - final long ident = Binder.clearCallingIdentity(); - if (client != null) { - client.decCount(); - } - Binder.restoreCallingIdentity(ident); - } - - - private class ScoClient implements IBinder.DeathRecipient { - private IBinder mCb; // To be notified of client's death - private int mCreatorPid; - private int mStartcount; // number of SCO connections started by this client - - ScoClient(IBinder cb) { - mCb = cb; - mCreatorPid = Binder.getCallingPid(); - mStartcount = 0; - } - - public void binderDied() { - synchronized(mScoClients) { - Log.w(TAG, "SCO client died"); - int index = mScoClients.indexOf(this); - if (index < 0) { - Log.w(TAG, "unregistered SCO client died"); - } else { - clearCount(true); - mScoClients.remove(this); - } - } - } - - public void incCount(int scoAudioMode) { - synchronized(mScoClients) { - requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); - if (mStartcount == 0) { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); - } - } - mStartcount++; - } - } - - public void decCount() { - synchronized(mScoClients) { - if (mStartcount == 0) { - Log.w(TAG, "ScoClient.decCount() already 0"); - } else { - mStartcount--; - if (mStartcount == 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "decCount() going to 0 but not registered to binder"); - } - } - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public void clearCount(boolean stopSco) { - synchronized(mScoClients) { - if (mStartcount != 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder"); - } - } - mStartcount = 0; - if (stopSco) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public int getCount() { - return mStartcount; - } - - public IBinder getBinder() { - return mCb; - } - - public int getPid() { - return mCreatorPid; - } - - public int totalCount() { - synchronized(mScoClients) { - int count = 0; - for (ScoClient mScoClient : mScoClients) { - count += mScoClient.getCount(); - } - return count; - } - } - - private void requestScoState(int state, int scoAudioMode) { - checkScoAudioState(); - int clientCount = totalCount(); - if (clientCount != 0) { - Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode - + ", clientCount=" + clientCount); - return; - } - if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - // Make sure that the state transitions to CONNECTING even if we cannot initiate - // the connection. - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); - // Accept SCO audio activation only in NORMAL audio mode or if the mode is - // currently controlled by the same client process. - synchronized(mSetModeDeathHandlers) { - int modeOwnerPid = mSetModeDeathHandlers.isEmpty() - ? 0 : mSetModeDeathHandlers.get(0).getPid(); - if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { - Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " - + modeOwnerPid + " != creatorPid " + mCreatorPid); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return; - } - switch (mScoAudioState) { - case SCO_STATE_INACTIVE: - mScoAudioMode = scoAudioMode; - if (scoAudioMode == SCO_MODE_UNDEFINED) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - if (mBluetoothHeadsetDevice != null) { - mScoAudioMode = Settings.Global.getInt(mContentResolver, - "bluetooth_sco_channel_" - + mBluetoothHeadsetDevice.getAddress(), - SCO_MODE_VIRTUAL_CALL); - if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - } - } - } - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " connection, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - Log.w(TAG, "requestScoState: no active device while connecting," - + " mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } else { - Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice - + " failed, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_DEACTIVATING: - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - break; - case SCO_STATE_DEACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to connect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - - } - } - } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVE_INTERNAL: - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_DEACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " disconnection, mScoAudioMode=" + mScoAudioMode); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } else { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to disconnect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - } - } - } - - private void checkScoAudioState() { - synchronized (mScoClients) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && - mScoAudioState == SCO_STATE_INACTIVE && - mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - } - } - - - private ScoClient getScoClient(IBinder cb, boolean create) { - synchronized(mScoClients) { - for (ScoClient existingClient : mScoClients) { - if (existingClient.getBinder() == cb) { - return existingClient; - } - } - if (create) { - ScoClient newClient = new ScoClient(cb); - mScoClients.add(newClient); - return newClient; - } - return null; - } - } - - public void clearAllScoClients(int exceptPid, boolean stopSco) { - synchronized(mScoClients) { - ScoClient savedClient = null; - for (ScoClient cl : mScoClients) { - if (cl.getPid() != exceptPid) { - cl.clearCount(stopSco); - } else { - savedClient = cl; - } - } - mScoClients.clear(); - if (savedClient != null) { - mScoClients.add(savedClient); - } - } - } - - private boolean getBluetoothHeadset() { - boolean result = false; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - } - // If we could not get a bluetooth headset proxy, send a failure message - // without delay to reset the SCO audio state and clear SCO clients. - // If we could get a proxy, send a delayed failure message that will reset our state - // in case we don't receive onServiceConnected(). - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, - SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0); - return result; - } - - /** - * Disconnect all SCO connections started by {@link AudioManager} except those started by - * {@param exceptPid} - * - * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept - */ - private void disconnectBluetoothSco(int exceptPid) { - synchronized(mScoClients) { - checkScoAudioState(); - if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { - return; - } - clearAllScoClients(exceptPid, true); - } - } - - private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.disconnectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.stopVoiceRecognition(device); - default: - return false; - } - } - - private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.connectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.startScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.startVoiceRecognition(device); - default: - return false; - } - } - - private void resetBluetoothSco() { - synchronized(mScoClients) { - clearAllScoClients(0, false); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - AudioSystem.setParameters("A2dpSuspended=false"); - setBluetoothScoOnInt(false, "resetBluetoothSco"); - } - - private void broadcastScoConnectionState(int state) { - sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE, - SENDMSG_QUEUE, state, 0, null, 0); - } - - private void onBroadcastScoConnectionState(int state) { - if (state != mScoConnectionState) { - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, - mScoConnectionState); - sendStickyBroadcastToAll(newIntent); - mScoConnectionState = state; - } - } - - private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { - if (btDevice == null) { - return true; - } - String address = btDevice.getAddress(); - BluetoothClass btClass = btDevice.getBluetoothClass(); - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - int[] outDeviceTypes = { - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; - if (btClass != null) { - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; - break; - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; - break; - } - } - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - String btDeviceName = btDevice.getName(); - boolean result = false; - if (isActive) { - result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName); - } else { - for (int outDeviceType : outDeviceTypes) { - result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName); - } - } - // handleDeviceConnection() && result to make sure the method get executed - result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result; - return result; - } - - private void setBtScoActiveDevice(BluetoothDevice btDevice) { - synchronized (mScoClients) { - Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); - final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; - if (!Objects.equals(btDevice, previousActiveDevice)) { - if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { - Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " - + previousActiveDevice); - } - if (!handleBtScoActiveDeviceChange(btDevice, true)) { - Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); - // set mBluetoothHeadsetDevice to null when failing to add new device - btDevice = null; - } - mBluetoothHeadsetDevice = btDevice; - if (mBluetoothHeadsetDevice == null) { - resetBluetoothSco(); - } - } - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - BluetoothDevice btDevice; - List<BluetoothDevice> deviceList; - switch(profile) { - case BluetoothProfile.A2DP: - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - mA2dp = (BluetoothA2dp) proxy; - deviceList = mA2dp.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mA2dp.getConnectionState(btDevice); - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, - AudioSystem.DEVICE_NONE); - final String addr = btDevice == null ? "null" : btDevice.getAddress(); - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2DP service connected: device addr=" + addr - + " state=" + state)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SINK_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - delay); - } - } - } - break; - - case BluetoothProfile.A2DP_SINK: - deviceList = proxy.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - synchronized (mConnectedDevices) { - int state = proxy.getConnectionState(btDevice); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SRC_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - 0 /* delay */); - } - } - break; - - case BluetoothProfile.HEADSET: - synchronized (mScoClients) { - // Discard timeout message - mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); - mBluetoothHeadset = (BluetoothHeadset) proxy; - setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); - // Refresh SCO audio state - checkScoAudioState(); - // Continue pending action if any - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { - boolean status = false; - if (mBluetoothHeadsetDevice != null) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - status = connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } - break; - case SCO_STATE_DEACTIVATE_REQ: - status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } - break; - } - } - if (!status) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - } - } - break; - - case BluetoothProfile.HEARING_AID: - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - mHearingAid = (BluetoothHearingAid) proxy; - deviceList = mHearingAid.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mHearingAid.getConnectionState(btDevice); - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_HEARING_AID, intState, - AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - btDevice, - delay); - } - } - } - - break; - - default: - break; - } - } - public void onServiceDisconnected(int profile) { - - switch (profile) { - case BluetoothProfile.A2DP: - disconnectA2dp(); - break; - - case BluetoothProfile.A2DP_SINK: - disconnectA2dpSink(); - break; - - case BluetoothProfile.HEADSET: - disconnectHeadset(); - break; - - case BluetoothProfile.HEARING_AID: - disconnectHearingAid(); - break; - - default: - break; - } - } - }; - - void disconnectAllBluetoothProfiles() { - disconnectA2dp(); - disconnectA2dpSink(); - disconnectHeadset(); - disconnectHearingAid(); - } - - void disconnectA2dp() { - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay); - } - } - } - } - } - - void disconnectA2dpSink() { - synchronized (mConnectedDevices) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices - for(int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpSrcUnavailable(toRemove.valueAt(i)); - } - } - } + final String eventSource = new StringBuilder("stopBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); } - void disconnectHeadset() { - synchronized (mScoClients) { - setBtScoActiveDevice(null); - mBluetoothHeadset = null; - } - } - void disconnectHearingAid() { - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_HEARING_AID devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/); - } - } - } - } + /*package*/ ContentResolver getContentResolver() { + return mContentResolver; } private void onCheckMusicActive(String caller) { @@ -4173,8 +3312,8 @@ public class AudioService extends IAudioService.Stub caller, MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); - if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && - (index > safeMediaVolumeIndex(device))) { + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) + && (index > safeMediaVolumeIndex(device))) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { @@ -4192,8 +3331,7 @@ public class AudioService extends IAudioService.Stub mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } - private int getSafeUsbMediaVolumeIndex() - { + private int getSafeUsbMediaVolumeIndex() { // determine UI volume index corresponding to the wanted safe gain in dBFS int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; @@ -4201,7 +3339,7 @@ public class AudioService extends IAudioService.Stub mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f; - while (Math.abs(max-min) > 1) { + while (Math.abs(max - min) > 1) { int index = (max + min) / 2; float gainDB = AudioSystem.getStreamVolumeDB( AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); @@ -4518,7 +3656,7 @@ public class AudioService extends IAudioService.Stub || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } - private boolean isInCommunication() { + /*package*/ boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = @@ -4671,25 +3809,9 @@ public class AudioService extends IAudioService.Stub } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { return; } - synchronized (mLastDeviceConnectMsgTime) { - long time = SystemClock.uptimeMillis() + delay; - - if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || - msg == MSG_SET_A2DP_SINK_CONNECTION_STATE || - msg == MSG_SET_HEARING_AID_CONNECTION_STATE || - msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || - msg == MSG_A2DP_DEVICE_CONFIG_CHANGE || - msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE || - msg == MSG_BTA2DP_DOCK_TIMEOUT) { - if (mLastDeviceConnectMsgTime >= time) { - // add a little delay to make sure messages are ordered as expected - time = mLastDeviceConnectMsgTime + 30; - } - mLastDeviceConnectMsgTime = time; - } - handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); - } + final long time = SystemClock.uptimeMillis() + delay; + handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } boolean checkAudioSettingsPermission(String method) { @@ -4704,7 +3826,7 @@ public class AudioService extends IAudioService.Stub return false; } - private int getDeviceForStream(int stream) { + /*package*/ int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: @@ -4749,160 +3871,94 @@ public class AudioService extends IAudioService.Stub } } - private int getA2dpCodec(BluetoothDevice device) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); - if (btCodecStatus == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); - if (btCodecConfig == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); - } + + /*package*/ void observeDevicesForAllStreams() { + observeDevicesForStreams(-1); } - /* - * A class just for packaging up a set of connection parameters. + /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0; + /*package*/ static final int CONNECTION_STATE_CONNECTED = 1; + /** + * The states that can be used with AudioService.setWiredDeviceConnectionState() + * Attention: those values differ from those in BluetoothProfile, follow annotations to + * distinguish between @ConnectionState and @BtProfileConnectionState */ - class WiredDeviceConnectionState { - public final int mType; - public final int mState; - public final String mAddress; - public final String mName; - public final String mCaller; + @IntDef({ + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectionState {} - public WiredDeviceConnectionState(int type, int state, String address, String name, - String caller) { - mType = type; - mState = state; - mAddress = address; - mName = name; - mCaller = caller; - } - } - - public void setWiredDeviceConnectionState(int type, int state, String address, String name, + /** + * see AudioManager.setWiredDeviceConnectionState() + */ + public void setWiredDeviceConnectionState(int type, + @ConnectionState int state, String address, String name, String caller) { - synchronized (mConnectedDevices) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:" - + address + ")"); - } - int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_WIRED_DEVICE_CONNECTION_STATE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new WiredDeviceConnectionState(type, state, address, name, caller), - delay); + if (state != CONNECTION_STATE_CONNECTED + && state != CONNECTION_STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } + mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); } + /** + * @hide + * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState() + * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + @IntDef({ + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileConnectionState {} + public int setBluetoothHearingAidDeviceConnectionState( - BluetoothDevice device, int state, boolean suppressNoisyIntent, - int musicDevice) + @NonNull BluetoothDevice device, @BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { - int delay; - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setHearingAidDeviceConnectionState state=" + state - + " addr=" + device.getAddress() - + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG)); - synchronized (mConnectedDevices) { - if (!suppressNoisyIntent) { - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - intState, musicDevice); - } else { - delay = 0; - } - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - device, - delay); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - return delay; - } - - public int setBluetoothA2dpDeviceConnectionState( - BluetoothDevice device, int state, int profile) - { - return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, false /* suppressNoisyIntent */, - -1 /* a2dpVolume */); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); + } + return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice, "AudioService"); } - public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device, - int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) - { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state - // only querying address as this is the only readily available field on the device - + " addr=" + device.getAddress() - + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent - + " vol=" + a2dpVolume)).printLog(TAG)); - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored")); - return 0; - } - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); - } - - public int setBluetoothA2dpDeviceConnectionStateInt( - BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, - int musicDevice, int a2dpVolume) - { - int delay; - if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { - throw new IllegalArgumentException("invalid profile " + profile); + /** + * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - synchronized (mConnectedDevices) { - if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - intState, musicDevice); - } else { - delay = 0; - } - - int a2dpCodec = getA2dpCodec(device); - - if (DEBUG_DEVICES) { - Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device - + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec - + " suppressNoisyIntent: " + suppressNoisyIntent); - } - - queueMsgUnderWakeLock(mAudioHandler, - (profile == BluetoothProfile.A2DP ? - MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE), - state, - 0, /* arg2 unused */ - new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec), - delay); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); } - return delay; + return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state, + profile, suppressNoisyIntent, a2dpVolume); } + /** + * See AudioManager.handleBluetoothA2dpDeviceConfigChange() + * @param device + */ public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) { - synchronized (mConnectedDevices) { - int a2dpCodec = getA2dpCodec(device); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_DEVICE_CONFIG_CHANGE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec), - 0 /* delay */); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } /** @@ -4912,52 +3968,18 @@ public class AudioService extends IAudioService.Stub public int handleBluetoothA2dpActiveDeviceChange( BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); + } if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { throw new IllegalArgumentException("invalid profile " + profile); } - - synchronized (mConnectedDevices) { - if (state == BluetoothA2dp.STATE_CONNECTED) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - continue; - } - // If A2DP device exists, this is either an active device change or - // device config change - String existingDevicekey = mConnectedDevices.keyAt(i); - String deviceName = device.getName(); - String address = device.getAddress(); - String newDeviceKey = makeDeviceListKey( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - int a2dpCodec = getA2dpCodec(device); - // Device not equal to existing device, active device change - if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { - mConnectedDevices.remove(existingDevicekey); - mConnectedDevices.put(newDeviceKey, new DeviceListSpec( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, - address, a2dpCodec)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_ACTIVE_DEVICE_CHANGE, - 0, - 0, - new BluetoothA2dpDeviceInfo( - device, a2dpVolume, a2dpCodec), - 0 /* delay */); - return 0; - } else { - // Device config change for existing device - handleBluetoothA2dpDeviceConfigChange(device); - return 0; - } - } - } + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } - - // New device connection or a device disconnect - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); + return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); } private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = @@ -4967,24 +3989,27 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; + /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { + sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, + newDevice, 0, null, 0); + } + private void onAccessoryPlugMediaUnmute(int newDevice) { if (DEBUG_VOL) { Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } - synchronized (mConnectedDevices) { - if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS - && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 - && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted - && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 - && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) - { - if (DEBUG_VOL) { - Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", - newDevice, AudioSystem.getOutputDeviceName(newDevice))); - } - mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); + + if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 + && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted + && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 + && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) { + if (DEBUG_VOL) { + Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", + newDevice, AudioSystem.getOutputDeviceName(newDevice))); } + mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); } } @@ -4994,7 +4019,7 @@ public class AudioService extends IAudioService.Stub // NOTE: Locking order for synchronized objects related to volume or ringer mode management: // 1 mScoclient OR mSafeMediaVolumeState - // 2 mSetModeDeathHandlers + // 2 mSetModeLock // 3 mSettingsLock // 4 VolumeStreamState.class public class VolumeStreamState { @@ -5138,11 +4163,11 @@ public class AudioService extends IAudioService.Stub } // must be called while synchronized VolumeStreamState.class - public void applyDeviceVolume_syncVSS(int device) { + /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) { int index; if (mIsMuted) { index = 0; - } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) { + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5151,10 +4176,11 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(device) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } public void applyAllVolumes() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // apply device specific volumes first int index; @@ -5164,7 +4190,7 @@ public class AudioService extends IAudioService.Stub if (mIsMuted) { index = 0; } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - mAvrcpAbsVolSupported) { + isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5173,7 +4199,7 @@ public class AudioService extends IAudioService.Stub } else { index = (mIndexMap.valueAt(i) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } } // apply default volume last: by convention , default device volume will be used @@ -5183,7 +4209,7 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; } - AudioSystem.setStreamVolumeIndex( + AudioSystem.setStreamVolumeIndexAS( mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); } } @@ -5373,6 +4399,7 @@ public class AudioService extends IAudioService.Stub } public void checkFixedVolumeDevices() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // ignore settings for fixed volume devices: volume should always be at max or 0 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) { @@ -5383,7 +4410,7 @@ public class AudioService extends IAudioService.Stub || (((device & mFixedVolumeDevices) != 0) && index != 0)) { mIndexMap.put(device, mIndexMax); } - applyDeviceVolume_syncVSS(device); + applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); } } } @@ -5465,11 +4492,13 @@ public class AudioService extends IAudioService.Stub } } - private void setDeviceVolume(VolumeStreamState streamState, int device) { + /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { + + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // Apply volume - streamState.applyDeviceVolume_syncVSS(device); + streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -5479,11 +4508,13 @@ public class AudioService extends IAudioService.Stub // Make sure volume is also maxed out on A2DP device for aliased stream // that may have a different device selected int streamDevice = getDeviceForStream(streamType); - if ((device != streamDevice) && mAvrcpAbsVolSupported && - ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { - mStreamStates[streamType].applyDeviceVolume_syncVSS(device); + if ((device != streamDevice) && isAvrcpAbsVolSupported + && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume_syncVSS(device, + isAvrcpAbsVolSupported); } - mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice); + mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice, + isAvrcpAbsVolSupported); } } } @@ -5762,12 +4793,6 @@ public class AudioService extends IAudioService.Stub } } - private void setForceUse(int usage, int config, String eventSource) { - synchronized (mConnectedDevices) { - setForceUseInt_SyncDevices(usage, config, eventSource); - } - } - private void onPersistSafeVolumeState(int state) { Settings.Global.putInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, @@ -5834,56 +4859,20 @@ public class AudioService extends IAudioService.Stub onPlaySoundEffect(msg.arg1, msg.arg2); break; - case MSG_BTA2DP_DOCK_TIMEOUT: - // msg.obj == address of BTA2DP device - synchronized (mConnectedDevices) { - makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); - } - mAudioEventWakeLock.release(); - break; - case MSG_SET_FORCE_USE: - case MSG_SET_FORCE_BT_A2DP_USE: - setForceUse(msg.arg1, msg.arg2, (String) msg.obj); - break; - - case MSG_BT_HEADSET_CNCT_FAILED: - resetBluetoothSco(); - break; - - case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: - { WiredDeviceConnectionState connectState = - (WiredDeviceConnectionState)msg.obj; - mDeviceLogger.log(new WiredDevConnectEvent(connectState)); - onSetWiredDeviceConnectionState(connectState.mType, connectState.mState, - connectState.mAddress, connectState.mName, connectState.mCaller); - mAudioEventWakeLock.release(); + { + final String eventSource = (String) msg.obj; + final int useCase = msg.arg1; + final int config = msg.arg2; + if (useCase == AudioSystem.FOR_MEDIA) { + Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from " + + eventSource); + break; } - break; - - case MSG_SET_A2DP_SRC_CONNECTION_STATE: - onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_A2DP_SINK_CONNECTION_STATE: - onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_HEARING_AID_CONNECTION_STATE: - onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_DEVICE_CONFIG_CHANGE: - onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_ACTIVE_DEVICE_CHANGE: - onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); + sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } break; case MSG_DISABLE_AUDIO_FOR_UID: @@ -5892,35 +4881,10 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; - case MSG_REPORT_NEW_ROUTES: { - int N = mRoutesObservers.beginBroadcast(); - if (N > 0) { - AudioRoutesInfo routes; - synchronized (mCurAudioRoutes) { - routes = new AudioRoutesInfo(mCurAudioRoutes); - } - while (N > 0) { - N--; - IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N); - try { - obs.dispatchAudioRoutesChanged(routes); - } catch (RemoteException e) { - } - } - } - mRoutesObservers.finishBroadcast(); - observeDevicesForStreams(-1); - break; - } - case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; - case MSG_BROADCAST_AUDIO_BECOMING_NOISY: - onSendBecomingNoisyIntent(); - break; - case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), @@ -5930,10 +4894,6 @@ public class AudioService extends IAudioService.Stub onPersistSafeVolumeState(msg.arg1); break; - case MSG_BROADCAST_BT_CONNECTION_STATE: - onBroadcastScoConnectionState(msg.arg1); - break; - case MSG_SYSTEM_READY: onSystemReady(); break; @@ -6033,20 +4993,7 @@ public class AudioService extends IAudioService.Stub if (mEncodedSurroundMode != newSurroundMode) { // Send to AudioPolicyManager sendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); - synchronized(mConnectedDevices) { - // Is HDMI connected? - String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); - DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec != null) { - // Toggle HDMI to retrigger broadcast with proper formats. - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", - "android"); // disconnect - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_AVAILABLE, "", "", - "android"); // reconnect - } - } + mDeviceBroker.toggleHdmiIfConnected_Async(); mEncodedSurroundMode = newSurroundMode; mSurroundModeChanged = true; } else { @@ -6055,515 +5002,18 @@ public class AudioService extends IAudioService.Stub } } - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceAvailable( - String address, String name, String eventSource, int a2dpCodec) { - // enable A2DP before notifying A2DP connection to avoid unnecessary processing in - // audio policy manager - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - setBluetoothA2dpOnInt(true, eventSource); - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); - // Reset A2DP suspend state each time a new sink is connected - AudioSystem.setParameters("A2dpSuspended=false"); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, a2dpCodec)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0); - setCurrentAudioRouteNameIfPossible(name); - } - - private void onSendBecomingNoisyIntent() { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); - sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { - if (address == null) { - return; - } - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = false; - } - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); - // Remove A2DP routes as well - setCurrentAudioRouteNameIfPossible(null); - if (mDockAddress == address) { - mDockAddress = null; - } - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { - // prevent any activity on the A2DP audio output to avoid unwanted - // reconnection of the sink. - AudioSystem.setParameters("A2dpSuspended=true"); - // Retrieve deviceSpec before removing device - String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat : - AudioSystem.AUDIO_FORMAT_DEFAULT; - // the device will be made unavailable later, so consider it disconnected right away - mConnectedDevices.remove(deviceKey); - // send the delayed message to make the device unavailable later - queueMsgUnderWakeLock(mAudioHandler, - MSG_BTA2DP_DOCK_TIMEOUT, - a2dpCodec, - 0, - address, - delayMs); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcAvailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); - } - - private void setHearingAidVolume(int index, int streamType) { - synchronized (mHearingAidLock) { - if (mHearingAid != null) { - //hearing aid expect volume value in range -128dB to 0dB - int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10, - AudioSystem.DEVICE_OUT_HEARING_AID); - if (gainDB < BT_HEARING_AID_GAIN_MIN) - gainDB = BT_HEARING_AID_GAIN_MIN; - mHearingAid.setVolume(gainDB); - } - } - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { - int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); - setHearingAidVolume(index, AudioSystem.STREAM_MUSIC); - - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name, - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0); - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, - mStreamStates[AudioSystem.STREAM_MUSIC], 0); - setCurrentAudioRouteNameIfPossible(name); - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); - // Remove Hearing Aid routes as well - setCurrentAudioRouteNameIfPossible(null); - } - - // must be called synchronized on mConnectedDevices - private void cancelA2dpDeviceTimeout() { - mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - // must be called synchronized on mConnectedDevices - private boolean hasScheduledA2dpDockTimeout() { - return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state - + " is dock: " + btDevice.isBluetoothDock()); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat); - } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - cancelA2dpDeviceTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if (hasScheduledA2dpDockTimeout() && mDockAddress != null) { - cancelA2dpDeviceTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress, - AudioSystem.AUDIO_FORMAT_DEFAULT); - } - } - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - "onSetA2dpSinkConnectionState"); - setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); - } - makeA2dpDeviceAvailable(address, btDevice.getName(), - "onSetA2dpSinkConnectionState", a2dpCodec); - } - } - } - - private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - - if (DEBUG_VOL) { - Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcAvailable(address); - } - } - } - - private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state) - { - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceAvailable(address, btDevice.getName(), - "onSetHearingAidConnectionState"); - } - } - } - - private void setCurrentAudioRouteNameIfPossible(String name) { - synchronized (mCurAudioRoutes) { - if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { - if (name != null || !isCurrentDeviceConnected()) { - mCurAudioRoutes.bluetoothName = name; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private boolean isCurrentDeviceConnected() { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) { - return true; - } - } - return false; - } - - private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpDeviceConfigChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - // Device is connected - if (deviceSpec.mDeviceCodecFormat != a2dpCodec) { - deviceSpec.mDeviceCodecFormat = a2dpCodec; - mConnectedDevices.replace(key, deviceSpec); - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" - + deviceSpec.mDeviceCodecFormat); - } - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), deviceSpec.mDeviceCodecFormat) - != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - - /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */ - public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpActiveDeviceChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - - // Device is connected - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, device, - "onBluetoothA2dpActiveDeviceChange"); - setDeviceVolume(streamState, device); - } - - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - public void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = support; - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); - } - } - - private boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) - + " address:" + address + " name:" + deviceName + ")"); - } - synchronized (mConnectedDevices) { - String deviceKey = makeDeviceListKey(device, address); - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceKey:" + deviceKey); - } - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - boolean isConnected = deviceSpec != null; - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected); - } - if (connect && !isConnected) { - final int res = AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - if (res != AudioSystem.AUDIO_STATUS_OK) { - Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + - " due to command error " + res ); - return false; - } - mConnectedDevices.put(deviceKey, new DeviceListSpec(device, - deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - device, 0, null, 0); - return true; - } else if (!connect && isConnected) { - AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - // always remove even if disconnection failed - mConnectedDevices.remove(deviceKey); - return true; - } - Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec=" - + deviceSpec + ", connect=" + connect); - } - return false; - } - - // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only - // sent if: - // - none of these devices are connected anymore after one is disconnected AND - // - the device being disconnected is actually used for music. - // Access synchronized on mConnectedDevices - int mBecomingNoisyIntentDevices = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI | - AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | - AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_HEARING_AID; - - // must be called before removing the device from mConnectedDevices - // Called synchronized on mConnectedDevices - // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying - // from AudioSystem - private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { - int delay = 0; - if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) { - int devices = 0; - for (int i = 0; i < mConnectedDevices.size(); i++) { - int dev = mConnectedDevices.valueAt(i).mDeviceType; - if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) - && ((dev & mBecomingNoisyIntentDevices) != 0)) { - devices |= dev; - } - } - if (musicDevice == AudioSystem.DEVICE_NONE) { - musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - } - // ignore condition on device being actually used for music when in communication - // because music routing is altered in this case. - // also checks whether media routing if affected by a dynamic policy - if (((device == musicDevice) || isInCommunication()) && (device == devices) - && !hasMediaDynamicPolicy()) { - mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY); - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); - delay = 1000; - } - } - - return delay; } /** * @return true if there is currently a registered dynamic mixing policy that affects media */ - private boolean hasMediaDynamicPolicy() { + /*package*/ boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; @@ -6578,213 +5028,25 @@ public class AudioService extends IAudioService.Stub } } - private void updateAudioRoutes(int device, int state) - { - int connType = 0; - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - connType = AudioRoutesInfo.MAIN_HEADSET; - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - connType = AudioRoutesInfo.MAIN_HEADPHONES; - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - connType = AudioRoutesInfo.MAIN_HDMI; - } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE|| - device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - connType = AudioRoutesInfo.MAIN_USB; - } - - synchronized (mCurAudioRoutes) { - if (connType != 0) { - int newConn = mCurAudioRoutes.mainType; - if (state != 0) { - newConn |= connType; - } else { - newConn &= ~connType; - } - if (newConn != mCurAudioRoutes.mainType) { - mCurAudioRoutes.mainType = newConn; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private void sendDeviceConnectionIntent(int device, int state, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + - " state:0x" + Integer.toHexString(state) + " address:" + address + - " name:" + deviceName + ");"); - } - Intent intent = new Intent(); - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 0); - } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", - AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); - } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) { - if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else { - // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing - return; - } - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - configureHdmiPlugIntent(intent, state); - } - - if (intent.getAction() == null) { - return; - } - - intent.putExtra(CONNECT_INTENT_KEY_STATE, state); - intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); - intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); - - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - - final long ident = Binder.clearCallingIdentity(); - try { - ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_ALL_USB; - - private void onSetWiredDeviceConnectionState(int device, int state, String address, - String deviceName, String caller) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) - + " state:" + Integer.toHexString(state) - + " address:" + address - + " deviceName:" + deviceName - + " caller: " + caller + ");"); - } - - synchronized (mConnectedDevices) { - if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { - setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0"); - } - - if (!handleDeviceConnection(state == 1, device, address, deviceName)) { - // change of connection state failed, bailout - return; - } - if (state != 0) { - if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { - setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0"); - } - if ((device & mSafeMediaVolumeDevices) != 0) { - sendMsg(mAudioHandler, - MSG_CHECK_MUSIC_ACTIVE, - SENDMSG_REPLACE, - 0, - 0, - caller, - MUSIC_ACTIVE_POLL_PERIOD_MS); - } - // Television devices without CEC service apply software volume on HDMI output - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; - checkAllFixedVolumeDevices(); - synchronized (mHdmiClientLock) { - if (mHdmiManager != null && mHdmiPlaybackClient != null) { - mHdmiCecSink = false; - mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); - } - } - } - if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) { - sendEnabledSurroundFormats(mContentResolver, true); - } - } else { - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - synchronized (mHdmiClientLock) { - if (mHdmiManager != null) { - mHdmiCecSink = false; - } - } - } - } - sendDeviceConnectionIntent(device, state, address, deviceName); - updateAudioRoutes(device, state); - } - } - - private void configureHdmiPlugIntent(Intent intent, int state) { - intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); - intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); - if (state == 1) { - ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); - int[] portGeneration = new int[1]; - int status = AudioSystem.listAudioPorts(ports, portGeneration); - if (status == AudioManager.SUCCESS) { - for (AudioPort port : ports) { - if (port instanceof AudioDevicePort) { - final AudioDevicePort devicePort = (AudioDevicePort) port; - if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI || - devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) { - // format the list of supported encodings - int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); - if (formats.length > 0) { - ArrayList<Integer> encodingList = new ArrayList(1); - for (int format : formats) { - // a format in the list can be 0, skip it - if (format != AudioFormat.ENCODING_INVALID) { - encodingList.add(format); - } - } - int[] encodingArray = new int[encodingList.size()]; - for (int i = 0 ; i < encodingArray.length ; i++) { - encodingArray[i] = encodingList.get(i); - } - intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); - } - // find the maximum supported number of channels - int maxChannels = 0; - for (int mask : devicePort.channelMasks()) { - int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); - if (channelCount > maxChannels) { - maxChannels = channelCount; - } - } - intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); - } - } - } - } + /*package*/ void checkMusicActive(int deviceType, String caller) { + if ((deviceType & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); } } - /* cache of the address of the last dock the device was connected to */ - private String mDockAddress; - /** * Receiver for misc intent broadcasts the Phone app cares about. */ private class AudioServiceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + final String action = intent.getAction(); int outDevice; int inDevice; int state; @@ -6812,75 +5074,16 @@ public class AudioService extends IAudioService.Stub } // Low end docks have a menu to enable or disable audio // (see mDockAudioMediaEnabled) - if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) && - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config, - "ACTION_DOCK_EVENT intent")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); + if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) + || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) + && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config, + "ACTION_DOCK_EVENT intent"); } mDockState = dockState; - } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { - BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - setBtScoActiveDevice(btDevice); - } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { - boolean broadcast = false; - int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - synchronized (mScoClients) { - int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - // broadcast intent if the connection was initated by AudioService - if (!mScoClients.isEmpty() && - (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || - mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATING)) { - broadcast = true; - } - switch (btState) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - setBluetoothScoOn(true); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - setBluetoothScoOn(false); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // startBluetoothSco called after stopBluetoothSco - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcast = false; - break; - } - } - // Tear down SCO if disconnected from external - clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - default: - // do not broadcast CONNECTING or invalid state - broadcast = false; - break; - } - } - if (broadcast) { - broadcastScoConnectionState(scoAudioState); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); - sendStickyBroadcastToAll(newIntent); - } + } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED) + || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + mDeviceBroker.receiveBtEvent(intent); } else if (action.equals(Intent.ACTION_SCREEN_ON)) { if (mMonitorRotation) { RotationHelper.enable(); @@ -6898,13 +5101,7 @@ public class AudioService extends IAudioService.Stub if (mUserSwitchedReceived) { // attempt to stop music playback for background user except on first user // switch (i.e. first boot) - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); + mDeviceBroker.broadcastBecomingNoisy(); } mUserSwitchedReceived = true; // the current audio focus owner is no longer valid @@ -6938,7 +5135,7 @@ public class AudioService extends IAudioService.Stub state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) { - disconnectAllBluetoothProfiles(); + mDeviceBroker.disconnectAllBluetoothProfiles(); } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { @@ -7178,22 +5375,17 @@ public class AudioService extends IAudioService.Stub // take new state into account for streams muted by ringer mode setRingerModeInt(getRingerModeInternal(), false); } - - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_SYSTEM, + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, - new String("handleConfigurationChanged"), - 0); - + "handleConfigurationChanged"); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); + } } mVolumeController.setLayoutDirection(config.getLayoutDirection()); @@ -7202,28 +5394,6 @@ public class AudioService extends IAudioService.Stub } } - // Handles request to override default use of A2DP for media. - // Must be called synchronized on mConnectedDevices - public void setBluetoothA2dpOnInt(boolean on, String eventSource) { - synchronized (mBluetoothA2dpEnabledLock) { - mBluetoothA2dpEnabled = on; - mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); - setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource); - } - } - - // Must be called synchronized on mConnectedDevices - private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) { - if (usage == AudioSystem.FOR_MEDIA) { - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource)); - AudioSystem.setForceUse(usage, config); - } - @Override public void setRingtonePlayer(IRingtonePlayer player) { mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null); @@ -7237,11 +5407,7 @@ public class AudioService extends IAudioService.Stub @Override public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { - synchronized (mCurAudioRoutes) { - AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); - mRoutesObservers.register(observer); - return routes; - } + return mDeviceBroker.startWatchingRoutes(observer); } @@ -7283,9 +5449,9 @@ public class AudioService extends IAudioService.Stub // the headset is compliant to EN 60950 with a max loudness of 100dB SPL. private int mSafeUsbMediaVolumeIndex; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, - private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | - AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_USB_HEADSET; + /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET + | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_USB_HEADSET; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. @@ -7438,9 +5604,8 @@ public class AudioService extends IAudioService.Stub mHdmiSystemAudioSupported = on; final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, - config, "setHdmiSystemAudioSupported")); - AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config, + "setHdmiSystemAudioSupported"); } device = getDevicesForStream(AudioSystem.STREAM_MUSIC); } @@ -7553,14 +5718,14 @@ public class AudioService extends IAudioService.Stub // logs for wired + A2DP device connections: // - wired: logged before onSetWiredDeviceConnectionState() is executed // - A2DP: logged at reception of method call - final private AudioEventLogger mDeviceLogger = new AudioEventLogger( - LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection"); + /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger( + LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI"); - final private AudioEventLogger mForceUseLogger = new AudioEventLogger( + static final AudioEventLogger sForceUseLogger = new AudioEventLogger( LOG_NB_EVENTS_FORCE_USE, "force use (logged before setForceUse() is executed)"); - final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, + static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, "volume changes (logged when command received by AudioService)"); final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY, @@ -7613,8 +5778,9 @@ public class AudioService extends IAudioService.Stub dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); - pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType)); - pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName); + pw.print(" mMainType=0x"); pw.println(Integer.toHexString( + mDeviceBroker.getCurAudioRoutes().mainType)); + pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName); pw.println("\nOther state:"); pw.print(" mVolumeController="); pw.println(mVolumeController); @@ -7630,7 +5796,8 @@ public class AudioService extends IAudioService.Stub pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); - pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); + pw.print(" mAvrcpAbsVolSupported="); + pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported()); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -7643,11 +5810,11 @@ public class AudioService extends IAudioService.Stub pw.println("\nEvent logs:"); mModeLogger.dump(pw); pw.println("\n"); - mDeviceLogger.dump(pw); + sDeviceLogger.dump(pw); pw.println("\n"); - mForceUseLogger.dump(pw); + sForceUseLogger.dump(pw); pw.println("\n"); - mVolumeLogger.dump(pw); + sVolumeLogger.dump(pw); } private static String safeMediaVolumeStateToString(int state) { @@ -8270,6 +6437,11 @@ public class AudioService extends IAudioService.Stub } //====================== + // Audio device management + //====================== + private final AudioDeviceBroker mDeviceBroker; + + //====================== // Audio policy proxy //====================== private static final class AudioDeviceArray { diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 9d9e35bdf2b2..7ccb45e97912 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -19,7 +19,7 @@ package com.android.server.audio; import android.media.AudioManager; import android.media.AudioSystem; -import com.android.server.audio.AudioService.WiredDeviceConnectionState; +import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState; public class AudioServiceEvents { @@ -89,9 +89,11 @@ public class AudioServiceEvents { } final static class VolumeEvent extends AudioEventLogger.Event { - final static int VOL_ADJUST_SUGG_VOL = 0; - final static int VOL_ADJUST_STREAM_VOL = 1; - final static int VOL_SET_STREAM_VOL = 2; + static final int VOL_ADJUST_SUGG_VOL = 0; + static final int VOL_ADJUST_STREAM_VOL = 1; + static final int VOL_SET_STREAM_VOL = 2; + static final int VOL_SET_HEARING_AID_VOL = 3; + static final int VOL_SET_AVRCP_VOL = 4; final int mOp; final int mStream; @@ -107,6 +109,24 @@ public class AudioServiceEvents { mCaller = caller; } + VolumeEvent(int op, int index, int gainDb) { + mOp = op; + mVal1 = index; + mVal2 = gainDb; + //unused + mStream = -1; + mCaller = null; + } + + VolumeEvent(int op, int index) { + mOp = op; + mVal1 = index; + //unused + mVal2 = 0; + mStream = -1; + mCaller = null; + } + @Override public String eventToString() { switch (mOp) { @@ -131,6 +151,15 @@ public class AudioServiceEvents { .append(" flags:0x").append(Integer.toHexString(mVal2)) .append(") from ").append(mCaller) .toString(); + case VOL_SET_HEARING_AID_VOL: + return new StringBuilder("setHearingAidVolume:") + .append(" index:").append(mVal1) + .append(" gain dB:").append(mVal2) + .toString(); + case VOL_SET_AVRCP_VOL: + return new StringBuilder("setAvrcpVolume:") + .append(" index:").append(mVal1) + .toString(); default: return new StringBuilder("FIXME invalid op:").append(mOp).toString(); } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java new file mode 100644 index 000000000000..bf325013c7da --- /dev/null +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -0,0 +1,949 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * @hide + * Class to encapsulate all communication with Bluetooth services + */ +public class BtHelper { + + private static final String TAG = "AS.BtHelper"; + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + BtHelper(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // List of clients having issued a SCO start request + private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>(); + + // BluetoothHeadset API to control SCO connection + private BluetoothHeadset mBluetoothHeadset; + + // Bluetooth headset device + private BluetoothDevice mBluetoothHeadsetDevice; + + // Indicate if SCO audio connection is currently active and if the initiator is + // audio service (internal) or bluetooth headset (external) + private int mScoAudioState; + // SCO audio state is not active + private static final int SCO_STATE_INACTIVE = 0; + // SCO audio activation request waiting for headset service to connect + private static final int SCO_STATE_ACTIVATE_REQ = 1; + // SCO audio state is active or starting due to a request from AudioManager API + private static final int SCO_STATE_ACTIVE_INTERNAL = 3; + // SCO audio deactivation request waiting for headset service to connect + private static final int SCO_STATE_DEACTIVATE_REQ = 4; + // SCO audio deactivation in progress, waiting for Bluetooth audio intent + private static final int SCO_STATE_DEACTIVATING = 5; + + // SCO audio state is active due to an action in BT handsfree (either voice recognition or + // in call audio) + private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; + + // Indicates the mode used for SCO audio connection. The mode is virtual call if the request + // originated from an app targeting an API version before JB MR2 and raw audio after that. + private int mScoAudioMode; + // SCO audio mode is undefined + /*package*/ static final int SCO_MODE_UNDEFINED = -1; + // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) + /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; + // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) + private static final int SCO_MODE_RAW = 1; + // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) + private static final int SCO_MODE_VR = 2; + + private static final int SCO_MODE_MAX = 2; + + // Current connection state indicated by bluetooth headset + private int mScoConnectionState; + + private static final int BT_HEARING_AID_GAIN_MIN = -128; + + @GuardedBy("mDeviceBroker.mHearingAidLock") + private BluetoothHearingAid mHearingAid; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private BluetoothA2dp mA2dp; + // If absolute volume is supported in AVRCP device + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private boolean mAvrcpAbsVolSupported = false; + + //---------------------------------------------------------------------- + /*package*/ static class BluetoothA2dpDeviceInfo { + private final @NonNull BluetoothDevice mBtDevice; + private final int mVolume; + private final int mCodec; + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { + this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); + } + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { + mBtDevice = btDevice; + mVolume = volume; + mCodec = codec; + } + + public @NonNull BluetoothDevice getBtDevice() { + return mBtDevice; + } + + public int getVolume() { + return mVolume; + } + + public int getCodec() { + return mCodec; + } + } + + //---------------------------------------------------------------------- + // Interface for AudioDeviceBroker + + /*package*/ void onSystemReady() { + mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; + resetBluetoothSco(); + getBluetoothHeadset(); + + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + sendStickyBroadcastToAll(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.A2DP); + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID); + } + } + + @GuardedBy("mBluetoothA2dpEnabledLock") + /*package*/ void onAudioServerDiedRestoreA2dp() { + final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + return (mA2dp != null && mAvrcpAbsVolSupported); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + mAvrcpAbsVolSupported = supported; + } + + /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + if (mA2dp == null) { + if (AudioService.DEBUG_VOL) { + Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); + return; + } + } + if (!mAvrcpAbsVolSupported) { + return; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10)); + mA2dp.setAvrcpAbsoluteVolume(index / 10); + } + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + if (mA2dp == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + if (btCodecStatus == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); + if (btCodecConfig == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); + } + + /*package*/ void receiveBtEvent(Intent intent) { + final String action = intent.getAction(); + if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { + BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + setBtScoActiveDevice(btDevice); + } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + boolean broadcast = false; + int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; + synchronized (mScoClients) { + int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + // broadcast intent if the connection was initated by AudioService + if (!mScoClients.isEmpty() + && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL + || mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATING)) { + broadcast = true; + } + switch (btState) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // startBluetoothSco called after stopBluetoothSco + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcast = false; + break; + } + } + // Tear down SCO if disconnected from external + clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; + } + } + if (broadcast) { + broadcastScoConnectionState(scoAudioState); + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); + sendStickyBroadcastToAll(newIntent); + } + } + } + + /** + * + * @return false if SCO isn't connected + */ + /*package*/ boolean isBluetoothScoOn() { + synchronized (mScoClients) { + if ((mBluetoothHeadset != null) + && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { + Log.w(TAG, "isBluetoothScoOn(true) returning false because " + + mBluetoothHeadsetDevice + " is not in audio connected mode"); + return false; + } + } + return true; + } + + /** + * Disconnect all SCO connections started by {@link AudioManager} except those started by + * {@param exceptPid} + * + * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept + */ + /*package*/ void disconnectBluetoothSco(int exceptPid) { + synchronized (mScoClients) { + checkScoAudioState(); + if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { + return; + } + clearAllScoClients(exceptPid, true); + } + } + + /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + ScoClient client = getScoClient(cb, true); + // The calling identity must be cleared before calling ScoClient.incCount(). + // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + // The caller identity must be cleared after getScoClient() because it is needed if a new + // client is created. + final long ident = Binder.clearCallingIdentity(); + try { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.incCount(scoAudioMode); + } catch (NullPointerException e) { + Log.e(TAG, "Null ScoClient", e); + } + Binder.restoreCallingIdentity(ident); + } + + /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) { + ScoClient client = getScoClient(cb, false); + // The calling identity must be cleared before calling ScoClient.decCount(). + // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + final long ident = Binder.clearCallingIdentity(); + if (client != null) { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.decCount(); + } + Binder.restoreCallingIdentity(ident); + } + + + /*package*/ void setHearingAidVolume(int index, int streamType) { + synchronized (mDeviceBroker.mHearingAidLock) { + if (mHearingAid == null) { + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: null mHearingAid"); + } + return; + } + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, + AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) { + gainDB = BT_HEARING_AID_GAIN_MIN; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" + + index + " gain=" + gainDB); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); + mHearingAid.setVolume(gainDB); + } + } + + //---------------------------------------------------------------------- + private void broadcastScoConnectionState(int state) { + mDeviceBroker.broadcastScoConnectionState(state); + } + + /*package*/ void onBroadcastScoConnectionState(int state) { + if (state == mScoConnectionState) { + return; + } + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, + mScoConnectionState); + sendStickyBroadcastToAll(newIntent); + mScoConnectionState = state; + } + + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { + if (btDevice == null) { + return true; + } + String address = btDevice.getAddress(); + BluetoothClass btClass = btDevice.getBluetoothClass(); + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + int[] outDeviceTypes = { + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT + }; + if (btClass != null) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; + break; + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; + break; + } + } + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + String btDeviceName = btDevice.getName(); + boolean result = false; + if (isActive) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceTypes[0], address, btDeviceName); + } else { + for (int outDeviceType : outDeviceTypes) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceType, address, btDeviceName); + } + } + // handleDeviceConnection() && result to make sure the method get executed + result = mDeviceBroker.handleDeviceConnection( + isActive, inDevice, address, btDeviceName) && result; + return result; + } + + private void setBtScoActiveDevice(BluetoothDevice btDevice) { + synchronized (mScoClients) { + Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); + final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; + if (Objects.equals(btDevice, previousActiveDevice)) { + return; + } + if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { + Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " + + previousActiveDevice); + } + if (!handleBtScoActiveDeviceChange(btDevice, true)) { + Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); + // set mBluetoothHeadsetDevice to null when failing to add new device + btDevice = null; + } + mBluetoothHeadsetDevice = btDevice; + if (mBluetoothHeadsetDevice == null) { + resetBluetoothSco(); + } + } + } + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + final BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + if (btDevice == null) { + Log.e(TAG, "Invalid null device in BT profile listener"); + return; + } + final @BluetoothProfile.BtProfileState int state = + mA2dp.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSinkConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + } + break; + + case BluetoothProfile.A2DP_SINK: + deviceList = proxy.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + proxy.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSourceConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); + mBluetoothHeadset = (BluetoothHeadset) proxy; + setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + status = connectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } + break; + case SCO_STATE_DEACTIVATE_REQ: + status = disconnectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } + break; + } + } + if (!status) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + } + break; + + case BluetoothProfile.HEARING_AID: + mHearingAid = (BluetoothHearingAid) proxy; + deviceList = mHearingAid.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + mHearingAid.getConnectionState(btDevice); + mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + btDevice, state, + /*suppressNoisyIntent*/ false, + /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, + /*eventSource*/ "mBluetoothProfileServiceListener"); + } + break; + + default: + break; + } + } + public void onServiceDisconnected(int profile) { + + switch (profile) { + case BluetoothProfile.A2DP: + mDeviceBroker.handleDisconnectA2dp(); + break; + + case BluetoothProfile.A2DP_SINK: + mDeviceBroker.handleDisconnectA2dpSink(); + break; + + case BluetoothProfile.HEADSET: + disconnectHeadset(); + break; + + case BluetoothProfile.HEARING_AID: + mDeviceBroker.handleDisconnectHearingAid(); + break; + + default: + break; + } + } + }; + + void disconnectAllBluetoothProfiles() { + mDeviceBroker.handleDisconnectA2dp(); + mDeviceBroker.handleDisconnectA2dpSink(); + disconnectHeadset(); + mDeviceBroker.handleDisconnectHearingAid(); + } + + private void disconnectHeadset() { + synchronized (mScoClients) { + setBtScoActiveDevice(null); + mBluetoothHeadset = null; + } + } + + //---------------------------------------------------------------------- + private class ScoClient implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private int mCreatorPid; + private int mStartcount; // number of SCO connections started by this client + + ScoClient(IBinder cb) { + mCb = cb; + mCreatorPid = Binder.getCallingPid(); + mStartcount = 0; + } + + public void binderDied() { + synchronized (mScoClients) { + Log.w(TAG, "SCO client died"); + int index = mScoClients.indexOf(this); + if (index < 0) { + Log.w(TAG, "unregistered SCO client died"); + } else { + clearCount(true); + mScoClients.remove(this); + } + } + } + + public void incCount(int scoAudioMode) { + synchronized (mScoClients) { + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + if (mStartcount == 0) { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "ScoClient incCount() could not link to " + + mCb + " binder death"); + } + } + mStartcount++; + } + } + + public void decCount() { + synchronized (mScoClients) { + if (mStartcount == 0) { + Log.w(TAG, "ScoClient.decCount() already 0"); + } else { + mStartcount--; + if (mStartcount == 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "decCount() going to 0 but not registered to binder"); + } + } + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public void clearCount(boolean stopSco) { + synchronized (mScoClients) { + if (mStartcount != 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "clearCount() mStartcount: " + + mStartcount + " != 0 but not registered to binder"); + } + } + mStartcount = 0; + if (stopSco) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public int getCount() { + return mStartcount; + } + + public IBinder getBinder() { + return mCb; + } + + public int getPid() { + return mCreatorPid; + } + + public int totalCount() { + synchronized (mScoClients) { + int count = 0; + for (ScoClient mScoClient : mScoClients) { + count += mScoClient.getCount(); + } + return count; + } + } + + private void requestScoState(int state, int scoAudioMode) { + checkScoAudioState(); + int clientCount = totalCount(); + if (clientCount != 0) { + Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode + + ", clientCount=" + clientCount); + return; + } + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + // Accept SCO audio activation only in NORMAL audio mode or if the mode is + // currently controlled by the same client process. + synchronized (mDeviceBroker.mSetModeLock) { + int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty() + ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid(); + if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { + Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " + + modeOwnerPid + " != creatorPid " + mCreatorPid); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return; + } + switch (mScoAudioState) { + case SCO_STATE_INACTIVE: + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = Settings.Global.getInt( + mDeviceBroker.getContentResolver(), + "bluetooth_sco_channel_" + + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + } + } + } + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " connection, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + Log.w(TAG, "requestScoState: no active device while connecting," + + " mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } else { + Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice + + " failed, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_DEACTIVATING: + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + break; + case SCO_STATE_DEACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to connect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + + } + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVE_INTERNAL: + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " disconnection, mScoAudioMode=" + mScoAudioMode); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to disconnect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + } + } + } + + //----------------------------------------------------- + // Utilities + private void sendStickyBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.disconnectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.stopVoiceRecognition(device); + default: + return false; + } + } + + private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.connectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.startScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.startVoiceRecognition(device); + default: + return false; + } + } + + /*package*/ void resetBluetoothSco() { + synchronized (mScoClients) { + clearAllScoClients(0, false); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + AudioSystem.setParameters("A2dpSuspended=false"); + mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); + } + + + private void checkScoAudioState() { + synchronized (mScoClients) { + if (mBluetoothHeadset != null + && mBluetoothHeadsetDevice != null + && mScoAudioState == SCO_STATE_INACTIVE + && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + } + } + + + private ScoClient getScoClient(IBinder cb, boolean create) { + synchronized (mScoClients) { + for (ScoClient existingClient : mScoClients) { + if (existingClient.getBinder() == cb) { + return existingClient; + } + } + if (create) { + ScoClient newClient = new ScoClient(cb); + mScoClients.add(newClient); + return newClient; + } + return null; + } + } + + private void clearAllScoClients(int exceptPid, boolean stopSco) { + synchronized (mScoClients) { + ScoClient savedClient = null; + for (ScoClient cl : mScoClients) { + if (cl.getPid() != exceptPid) { + cl.clearCount(stopSco); + } else { + savedClient = cl; + } + } + mScoClients.clear(); + if (savedClient != null) { + mScoClients.add(savedClient); + } + } + } + + private boolean getBluetoothHeadset() { + boolean result = false; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + result = adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); + } + // If we could not get a bluetooth headset proxy, send a failure message + // without delay to reset the SCO audio state and clear SCO clients. + // If we could get a proxy, send a delayed failure message that will reset our state + // in case we don't receive onServiceConnected(). + mDeviceBroker.handleFailureToConnectToBtHeadsetService( + result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0); + return result; + } + + private int mapBluetoothCodecToAudioFormat(int btCodecType) { + switch (btCodecType) { + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: + return AudioSystem.AUDIO_FORMAT_SBC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: + return AudioSystem.AUDIO_FORMAT_AAC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: + return AudioSystem.AUDIO_FORMAT_APTX; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: + return AudioSystem.AUDIO_FORMAT_APTX_HD; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: + return AudioSystem.AUDIO_FORMAT_LDAC; + default: + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + } +} diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index dc564ba69e0d..3a25d980e97a 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -202,7 +202,7 @@ public final class PlaybackActivityMonitor if (mPrivilegedAlarmActiveCount++ == 0) { mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER); - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED && @@ -211,7 +211,7 @@ public final class PlaybackActivityMonitor if (AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) == mMaxAlarmVolume) { - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 41fedc5fab45..15d66e6e646f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -29,10 +29,12 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.IActivityTaskManager; +import android.app.KeyguardManager; import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; @@ -377,6 +379,13 @@ public class BiometricService extends SystemService { new BiometricTaskStackListener(); private final Random mRandom = new Random(); + // TODO(b/123378871): Remove when moved. + // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app) + // receiver. BiometricService internally launches CDCA which invokes BiometricService to + // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl + // method to poke BiometricService - the result will then be forwarded to this receiver. + private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; + // The current authentication session, null if idle/done. We need to track both the current // and pending sessions since errors may be sent to either. private AuthSession mCurrentAuthSession; @@ -705,6 +714,22 @@ public class BiometricService extends SystemService { } } + // Launch CDC instead if necessary. CDC will return results through an AIDL call, since + // we can't get activity results. Store the receiver somewhere so we can forward the + // result back to the client. + // TODO(b/123378871): Remove when moved. + if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) { + mConfirmDeviceCredentialReceiver = receiver; + final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class); + // Use this so we don't need to duplicate logic.. + final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */, + null /* description */); + // Then give it the bundle to do magic behavior.. + intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle); + getContext().startActivityAsUser(intent, UserHandle.CURRENT); + return; + } + mHandler.post(() -> { final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); final int modality = result.first; @@ -745,6 +770,36 @@ public class BiometricService extends SystemService { }); } + @Override // Binder call + public void onConfirmDeviceCredentialSuccess() { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCASuccess null!"); + return; + } + try { + mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + + @Override // Binder call + public void onConfirmDeviceCredentialError(int error, String message) { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + return; + } + try { + mConfirmDeviceCredentialReceiver.onError(error, message); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + /** * authenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 3db6a74a1c6c..fcf821f23ae9 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -48,14 +48,12 @@ import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; -import android.util.StatsLog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; -import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; @@ -588,11 +586,6 @@ public class FingerprintService extends BiometricServiceBase { public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) { mHandler.post(() -> { FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode); - if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE - && getCurrentClient() instanceof AuthenticationClient) { - // Ignore enrollment acquisitions or acquisitions when we are locked out. - StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto); - } }); } @@ -602,22 +595,6 @@ public class FingerprintService extends BiometricServiceBase { mHandler.post(() -> { Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId); FingerprintService.super.handleAuthenticated(fp, token); - // Send authentication to statsd. - final boolean authenticated = fingerId != 0; - StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto, - authenticated); - if (!authenticated) { - // If we failed to authenticate because of a lockout, inform statsd. - final int lockoutMode = getLockoutMode(); - if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) { - StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId, - mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT); - } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) { - StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId, - mIsCrypto, - StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT); - } - } }); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index ac745985417e..79b56c6027f8 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -20,7 +20,6 @@ import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; -import android.net.ip.IpClient; import android.net.metrics.ApfProgramEvent; import android.net.metrics.IpConnectivityLog; import android.os.Binder; @@ -270,8 +269,6 @@ final public class IpConnectivityMetrics extends SystemService { // Dump the rolling buffer of metrics event and pretty print events using a human readable // format. Also print network dns/connect statistics and default network event time series. static final String CMD_LIST = "list"; - // Dump all IpClient logs ("ipclient"). - static final String CMD_IPCLIENT = IpClient.DUMP_ARG; // By default any other argument will fall into the default case which is the equivalent // of calling both the "list" and "ipclient" commands. This includes most notably bug // reports collected by dumpsys.cpp with the "-a" argument. @@ -295,20 +292,9 @@ final public class IpConnectivityMetrics extends SystemService { case CMD_PROTO: cmdListAsProto(pw); return; - case CMD_IPCLIENT: { - final String[] ipclientArgs = ((args != null) && (args.length > 1)) - ? Arrays.copyOfRange(args, 1, args.length) - : null; - IpClient.dumpAllLogs(pw, ipclientArgs); - return; - } case CMD_LIST: - cmdList(pw); - return; default: cmdList(pw); - pw.println(""); - IpClient.dumpAllLogs(pw, null); return; } } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 8a3cdcad0230..1559ba8ba883 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -16,40 +16,49 @@ package com.android.server.connectivity; -import com.android.internal.util.HexDump; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.connectivity.NetworkAgentInfo; -import android.net.ConnectivityManager; +// TODO: Clean up imports and remove references of PacketKeepalive constants. + +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK; +import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT; +import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; +import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS; +import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; +import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.ConnectivityManager.PacketKeepalive; import android.net.KeepalivePacketData; -import android.net.LinkAddress; import android.net.NetworkAgent; import android.net.NetworkUtils; import android.net.util.IpUtils; import android.os.Binder; -import android.os.IBinder; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.Process; import android.os.RemoteException; -import android.system.OsConstants; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; import android.util.Pair; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.net.Inet4Address; -import java.net.Inet6Address; +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.HashMap; -import static android.net.ConnectivityManager.PacketKeepalive.*; -import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; - /** * Manages packet keepalive requests. * @@ -300,7 +309,9 @@ public class KeepaliveTracker { } } - public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { + /** Handle keepalive events from lower layer. */ + public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai, + @NonNull Message message) { int slot = message.arg1; int reason = message.arg2; @@ -330,8 +341,18 @@ public class KeepaliveTracker { } } - public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, - IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. See + * {@link android.net.SocketKeepalive}. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + int srcPort, + @NonNull String dstAddrString, + int dstPort) { if (nai == null) { notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); return; @@ -360,6 +381,56 @@ public class KeepaliveTracker { NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); } + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is + * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the + * resource index bound to the {@link UdpEncapsulationSocket} when creating by + * {@link com.android.server.IpSecService} to verify whether the given + * {@link UdpEncapsulationSocket} is legitimate. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + @Nullable FileDescriptor fd, + int resourceId, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + @NonNull String dstAddrString, + int dstPort) { + // Ensure that the socket is created by IpSecService. + if (!isNattKeepaliveSocketValid(fd, resourceId)) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Get src port to adopt old API. + int srcPort = 0; + try { + final SocketAddress srcSockAddr = Os.getsockname(fd); + srcPort = ((InetSocketAddress) srcSockAddr).getPort(); + } catch (ErrnoException e) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Forward request to old API. + startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort, + dstAddrString, dstPort); + } + + /** + * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid. + **/ + public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) { + // TODO: 1. confirm whether the fd is called from system api or created by IpSecService. + // 2. If the fd is created from the system api, check that it's bounded. And + // call dup to keep the fd open. + // 3. If the fd is created from IpSecService, check if the resource ID is valid. And + // hold the resource needed in IpSecService. + if (null == fd) { + return false; + } + return true; + } + public void dump(IndentingPrintWriter pw) { pw.println("Packet keepalives:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 9ea73fbb1882..d0cff25dbf05 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -152,6 +152,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Whether a captive portal was found during the last network validation attempt. public boolean lastCaptivePortalDetected; + // Indicates the user was notified of a successful captive portal login since a portal was + // last detected. + public boolean captivePortalLoginNotified; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being // satisfied by a higher-scoring network. so as to allow communication to wrap up before the // network is taken down. This usually only happens to the default network. Lingering ends with @@ -618,18 +622,19 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } public String toString() { - return "NetworkAgentInfo{ ni{" + networkInfo + "} " + - "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + - "lp{" + linkProperties + "} " + - "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + - "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + - "created{" + created + "} lingering{" + isLingering() + "} " + - "explicitlySelected{" + networkMisc.explicitlySelected + "} " + - "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + - "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + - "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + - "clat{" + clatd + "} " + - "}"; + return "NetworkAgentInfo{ ni{" + networkInfo + "} " + + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + + "lp{" + linkProperties + "} " + + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + + "created{" + created + "} lingering{" + isLingering() + "} " + + "explicitlySelected{" + networkMisc.explicitlySelected + "} " + + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " + + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} " + + "clat{" + clatd + "} " + + "}"; } public String name() { diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 36a2476d2ceb..b50477bc120f 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -16,13 +16,16 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.net.NetworkCapabilities; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.TelephonyManager; @@ -31,15 +34,12 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.widget.Toast; + import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; - public class NetworkNotificationManager { @@ -47,7 +47,8 @@ public class NetworkNotificationManager { LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET), NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH), NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET), - SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN); + SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN), + LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN); public final int eventId; @@ -192,6 +193,9 @@ public class NetworkNotificationManager { details = r.getString(R.string.network_available_sign_in_detailed, name); break; } + } else if (notifyType == NotificationType.LOGGED_IN) { + title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()); + details = r.getString(R.string.captive_portal_logged_in_detailed); } else if (notifyType == NotificationType.NETWORK_SWITCH) { String fromTransport = getTransportName(transportType); String toTransport = getTransportName(getFirstTransportType(switchToNai)); @@ -239,6 +243,18 @@ public class NetworkNotificationManager { } } + /** + * Clear the notification with the given id, only if it matches the given type. + */ + public void clearNotification(int id, NotificationType notifyType) { + final int previousEventId = mNotificationTypeMap.get(id); + final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId); + if (notifyType != previousNotifyType) { + return; + } + clearNotification(id); + } + public void clearNotification(int id) { if (mNotificationTypeMap.indexOfKey(id) < 0) { return; @@ -290,6 +306,10 @@ public class NetworkNotificationManager { return (t != null) ? t.name() : "UNKNOWN"; } + /** + * A notification with a higher number will take priority over a notification with a lower + * number. + */ private static int priority(NotificationType t) { if (t == null) { return 0; @@ -302,6 +322,7 @@ public class NetworkNotificationManager { case NETWORK_SWITCH: return 2; case LOST_INTERNET: + case LOGGED_IN: return 1; default: return 0; diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java index fdddccd20714..a671287324af 100644 --- a/services/core/java/com/android/server/connectivity/ProxyTracker.java +++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java @@ -309,22 +309,4 @@ public class ProxyTracker { } } } - - /** - * Enable or disable the default proxy. - * - * This sets the flag for enabling/disabling the default proxy and sends the broadcast - * if applicable. - * @param enabled whether the default proxy should be enabled. - */ - public void setDefaultProxyEnabled(final boolean enabled) { - synchronized (mProxyLock) { - if (mDefaultProxyEnabled != enabled) { - mDefaultProxyEnabled = enabled; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(); - } - } - } - } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index c72c9ddf3f7a..62a1b036daa0 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -793,6 +793,8 @@ public class Vpn { } } + lp.setHttpProxy(mConfig.proxyInfo); + if (!allowIPv4) { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 5ed626342d31..e268e4458986 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -48,7 +48,6 @@ import android.os.Build; import android.os.Bundle; import android.os.FactoryTest; import android.os.IBinder; -import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -255,21 +254,6 @@ public final class ContentService extends IContentService.Stub { } } - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (RuntimeException e) { - // The content service only throws security exceptions, so let's - // log all others. - if (!(e instanceof SecurityException)) { - Slog.wtf(TAG, "Content Service Crash", e); - } - throw e; - } - } - /*package*/ ContentService(Context context, boolean factoryTest) { mContext = context; mFactoryTest = factoryTest; diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java index 3a58160cae8d..eb0ed0a62ebf 100644 --- a/services/core/java/com/android/server/display/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/ColorDisplayService.java @@ -16,6 +16,13 @@ package com.android.server.display; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION; @@ -40,13 +47,17 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.ColorSpace; import android.hardware.display.ColorDisplayManager; +import android.hardware.display.ColorDisplayManager.AutoMode; +import android.hardware.display.ColorDisplayManager.ColorMode; import android.hardware.display.IColorDisplayManager; +import android.hardware.display.Time; import android.net.Uri; import android.opengl.Matrix; import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Secure; import android.provider.Settings.System; @@ -55,7 +66,6 @@ import android.util.Slog; import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; - import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ColorDisplayController; @@ -65,7 +75,6 @@ import com.android.server.SystemService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -103,59 +112,17 @@ public final class ColorDisplayService extends SystemService { private static final int MSG_APPLY_GLOBAL_SATURATION = 2; /** + * Return value if a setting has not been set. + */ + private static final int NOT_SET = -1; + + /** * Evaluator used to animate color matrix transitions. */ private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator(); - private final TintController mNightDisplayTintController = new TintController() { - - private float[] mMatrixNightDisplay = new float[16]; - private final float[] mColorTempCoefficients = new float[9]; - - /** - * Set coefficients based on whether the color matrix is linear or not. - */ - @Override - public void setUp(Context context, boolean needsLinear) { - final String[] coefficients = context.getResources().getStringArray(needsLinear - ? R.array.config_nightDisplayColorTemperatureCoefficients - : R.array.config_nightDisplayColorTemperatureCoefficientsNative); - for (int i = 0; i < 9 && i < coefficients.length; i++) { - mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); - } - } - - @Override - public void setMatrix(int cct) { - if (mMatrixNightDisplay.length != 16) { - Slog.d(TAG, "The display transformation matrix must be 4x4"); - return; - } - - Matrix.setIdentityM(mMatrixNightDisplay, 0); - - final float squareTemperature = cct * cct; - final float red = squareTemperature * mColorTempCoefficients[0] - + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2]; - final float green = squareTemperature * mColorTempCoefficients[3] - + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5]; - final float blue = squareTemperature * mColorTempCoefficients[6] - + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8]; - mMatrixNightDisplay[0] = red; - mMatrixNightDisplay[5] = green; - mMatrixNightDisplay[10] = blue; - } - - @Override - public float[] getMatrix() { - return isActivated() ? mMatrixNightDisplay : MATRIX_IDENTITY; - } - - @Override - public int getLevel() { - return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; - } - }; + private final NightDisplayTintController mNightDisplayTintController = + new NightDisplayTintController(); private final TintController mDisplayWhiteBalanceTintController = new TintController() { // Three chromaticity coordinates per color: X, Y, and Z @@ -198,7 +165,7 @@ public final class ColorDisplayService extends SystemService { } float[] displayRedGreenBlueXYZ = - new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; + new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) { displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]); @@ -209,10 +176,10 @@ public final class ColorDisplayService extends SystemService { } final ColorSpace.Rgb displayColorSpaceRGB = new ColorSpace.Rgb( - "Display Color Space", - displayRedGreenBlueXYZ, - displayWhiteXYZ, - 2.2f // gamma, unused for display white balance + "Display Color Space", + displayRedGreenBlueXYZ, + displayWhiteXYZ, + 2.2f // gamma, unused for display white balance ); float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; @@ -278,8 +245,8 @@ public final class ColorDisplayService extends SystemService { mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct); mChromaticAdaptationMatrix = - ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, - mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); + ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, + mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); // Convert the adaptation matrix to RGB space float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, @@ -386,7 +353,7 @@ public final class ColorDisplayService extends SystemService { float saturation = saturationLevel * 0.1f; float desaturation = 1.0f - saturation; float[] luminance = {0.231f * desaturation, 0.715f * desaturation, - 0.072f * desaturation}; + 0.072f * desaturation}; mMatrixGlobalSaturation[0] = luminance[0] + saturation; mMatrixGlobalSaturation[1] = luminance[0]; mMatrixGlobalSaturation[2] = luminance[0]; @@ -421,7 +388,7 @@ public final class ColorDisplayService extends SystemService { * subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's * ProgramCache for full implementation details. */ - private static final float[] MATRIX_INVERT_COLOR = new float[] { + private static final float[] MATRIX_INVERT_COLOR = new float[]{ 0.402f, -0.598f, -0.599f, 0f, -1.174f, -0.174f, -1.175f, 0f, -0.228f, -0.228f, 0.772f, 0f, @@ -445,7 +412,7 @@ public final class ColorDisplayService extends SystemService { public ColorDisplayService(Context context) { super(context); - mHandler = new TintHandler(Looper.getMainLooper()); + mHandler = new TintHandler(DisplayThread.get().getLooper()); } @Override @@ -548,23 +515,30 @@ public final class ColorDisplayService extends SystemService { if (setting != null) { switch (setting) { case Secure.NIGHT_DISPLAY_ACTIVATED: - onNightDisplayActivated(mNightDisplayController.isActivated()); + final boolean activated = isNightDisplayActivatedSetting(); + if (mNightDisplayTintController.isActivatedStateNotSet() + || mNightDisplayTintController.isActivated() != activated) { + mNightDisplayTintController.onActivated(activated); + } break; case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: - onNightDisplayColorTemperatureChanged( - mNightDisplayController.getColorTemperature()); + final int temperature = getNightDisplayColorTemperatureSetting(); + if (mNightDisplayTintController.getColorTemperature() + != temperature) { + mNightDisplayTintController + .onColorTemperatureChanged(temperature); + } break; case Secure.NIGHT_DISPLAY_AUTO_MODE: - onNightDisplayAutoModeChanged( - mNightDisplayController.getAutoMode()); + onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal()); break; case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME: onNightDisplayCustomStartTimeChanged( - mNightDisplayController.getCustomStartTime()); + getNightDisplayCustomStartTimeInternal().getLocalTime()); break; case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME: onNightDisplayCustomEndTimeChanged( - mNightDisplayController.getCustomEndTime()); + getNightDisplayCustomEndTimeInternal().getLocalTime()); break; case System.DISPLAY_COLOR_MODE: onDisplayColorModeChanged(mNightDisplayController.getColorMode()); @@ -624,14 +598,14 @@ public final class ColorDisplayService extends SystemService { // Prepare the night display color transformation matrix. mNightDisplayTintController .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix()); - mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature()); + mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting()); // Initialize the current auto mode. - onNightDisplayAutoModeChanged(mNightDisplayController.getAutoMode()); + onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal()); - // Force the initialization current activated state. + // Force the initialization of the current saved activation state. if (mNightDisplayTintController.isActivatedStateNotSet()) { - onNightDisplayActivated(mNightDisplayController.isActivated()); + mNightDisplayTintController.onActivated(isNightDisplayActivatedSetting()); } } @@ -665,23 +639,6 @@ public final class ColorDisplayService extends SystemService { } } - private void onNightDisplayActivated(boolean activated) { - if (mNightDisplayTintController.isActivatedStateNotSet() - || mNightDisplayTintController.isActivated() != activated) { - Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display"); - - mNightDisplayTintController.setActivated(activated); - - if (mNightDisplayAutoMode != null) { - mNightDisplayAutoMode.onActivated(activated); - } - - updateDisplayWhiteBalanceStatus(); - - applyTint(mNightDisplayTintController, false); - } - } - private void onNightDisplayAutoModeChanged(int autoMode) { Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode); @@ -690,9 +647,9 @@ public final class ColorDisplayService extends SystemService { mNightDisplayAutoMode = null; } - if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) { + if (autoMode == AUTO_MODE_CUSTOM_TIME) { mNightDisplayAutoMode = new CustomNightDisplayAutoMode(); - } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) { + } else if (autoMode == AUTO_MODE_TWILIGHT) { mNightDisplayAutoMode = new TwilightNightDisplayAutoMode(); } @@ -717,13 +674,8 @@ public final class ColorDisplayService extends SystemService { } } - private void onNightDisplayColorTemperatureChanged(int colorTemperature) { - mNightDisplayTintController.setMatrix(colorTemperature); - applyTint(mNightDisplayTintController, true); - } - private void onDisplayColorModeChanged(int mode) { - if (mode == -1) { + if (mode == NOT_SET) { return; } @@ -732,7 +684,7 @@ public final class ColorDisplayService extends SystemService { mNightDisplayTintController .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode)); - mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature()); + mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting()); updateDisplayWhiteBalanceStatus(); @@ -901,6 +853,71 @@ public final class ColorDisplayService extends SystemService { return availabilityFlags; } + private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) { + if (getNightDisplayAutoModeInternal() != autoMode) { + Secure.putStringForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + null, + mCurrentUser); + } + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser); + } + + private int getNightDisplayAutoModeInternal() { + int autoMode = getNightDisplayAutoModeRawInternal(); + if (autoMode == NOT_SET) { + autoMode = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayAutoMode); + } + if (autoMode != AUTO_MODE_DISABLED + && autoMode != AUTO_MODE_CUSTOM_TIME + && autoMode != AUTO_MODE_TWILIGHT) { + Slog.e(TAG, "Invalid autoMode: " + autoMode); + autoMode = AUTO_MODE_DISABLED; + } + return autoMode; + } + + private int getNightDisplayAutoModeRawInternal() { + return Secure + .getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, + NOT_SET, mCurrentUser); + } + + private Time getNightDisplayCustomStartTimeInternal() { + int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser); + if (startTimeValue == NOT_SET) { + startTimeValue = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayCustomStartTime); + } + return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000)); + } + + private boolean setNightDisplayCustomStartTimeInternal(Time startTime) { + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, + startTime.getLocalTime().toSecondOfDay() * 1000, + mCurrentUser); + } + + private Time getNightDisplayCustomEndTimeInternal() { + int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser); + if (endTimeValue == NOT_SET) { + endTimeValue = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayCustomEndTime); + } + return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000)); + } + + private boolean setNightDisplayCustomEndTimeInternal(Time endTime) { + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000, + mCurrentUser); + } + /** * Returns the last time the night display transform activation state was changed, or {@link * LocalDateTime#MIN} if night display has never been activated. @@ -930,6 +947,89 @@ public final class ColorDisplayService extends SystemService { .setSaturationLevel(packageName, mCurrentUser, saturationLevel); } + private void setColorModeInternal(@ColorMode int colorMode) { + if (!isColorModeAvailable(colorMode)) { + throw new IllegalArgumentException("Invalid colorMode: " + colorMode); + } + System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE, + colorMode, + mCurrentUser); + } + + private @ColorMode + int getColorModeInternal() { + final ContentResolver cr = getContext().getContentResolver(); + if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + 0, mCurrentUser) == 1 + || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 0, mCurrentUser) == 1) { + // There are restrictions on the available color modes combined with a11y transforms. + if (isColorModeAvailable(COLOR_MODE_SATURATED)) { + return COLOR_MODE_SATURATED; + } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { + return COLOR_MODE_AUTOMATIC; + } + } + + int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser); + if (colorMode == -1) { + // There might be a system property controlling color mode that we need to respect; if + // not, this will set a suitable default. + colorMode = getCurrentColorModeFromSystemProperties(); + } + + // This happens when a color mode is no longer available (e.g., after system update or B&R) + // or the device does not support any color mode. + if (!isColorModeAvailable(colorMode)) { + if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) { + colorMode = COLOR_MODE_NATURAL; + } else if (colorMode == COLOR_MODE_SATURATED + && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { + colorMode = COLOR_MODE_AUTOMATIC; + } else if (colorMode == COLOR_MODE_AUTOMATIC + && isColorModeAvailable(COLOR_MODE_SATURATED)) { + colorMode = COLOR_MODE_SATURATED; + } else { + colorMode = -1; + } + } + + return colorMode; + } + + /** + * Get the current color mode from system properties, or return -1 if invalid. + * + * See {@link com.android.server.display.DisplayTransformManager} + */ + private @ColorMode + int getCurrentColorModeFromSystemProperties() { + final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0); + if (displayColorSetting == 0) { + return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation")) + ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED; + } else if (displayColorSetting == 1) { + return COLOR_MODE_SATURATED; + } else if (displayColorSetting == 2) { + return COLOR_MODE_AUTOMATIC; + } else { + return -1; + } + } + + private boolean isColorModeAvailable(@ColorMode int colorMode) { + final int[] availableColorModes = getContext().getResources().getIntArray( + R.array.config_availableColorModes); + if (availableColorModes != null) { + for (int mode : availableColorModes) { + if (mode == colorMode) { + return true; + } + } + } + return false; + } + private void dumpInternal(PrintWriter pw) { pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)"); pw.println("Night Display:"); @@ -941,6 +1041,33 @@ public final class ColorDisplayService extends SystemService { mAppSaturationController.dump(pw); } + private boolean isNightDisplayActivatedSetting() { + return Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1; + } + + private int getNightDisplayColorTemperatureSetting() { + return clampNightDisplayColorTemperature(Secure.getIntForUser( + getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NOT_SET, + mCurrentUser)); + } + + private int clampNightDisplayColorTemperature(int colorTemperature) { + if (colorTemperature == NOT_SET) { + colorTemperature = getContext().getResources().getInteger( + R.integer.config_nightDisplayColorTemperatureDefault); + } + final int minimumTemperature = ColorDisplayManager.getMinimumColorTemperature(getContext()); + final int maximumTemperature = ColorDisplayManager.getMaximumColorTemperature(getContext()); + if (colorTemperature < minimumTemperature) { + colorTemperature = minimumTemperature; + } else if (colorTemperature > maximumTemperature) { + colorTemperature = maximumTemperature; + } + + return colorTemperature; + } + private abstract class NightDisplayAutoMode { public abstract void onActivated(boolean activated); @@ -987,13 +1114,13 @@ public final class ColorDisplayService extends SystemService { // Maintain the existing activated state if within the current period. if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start) && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) { - activate = mNightDisplayController.isActivated(); + activate = isNightDisplayActivatedSetting(); } } if (mNightDisplayTintController.isActivatedStateNotSet() || ( mNightDisplayTintController.isActivated() != activate)) { - mNightDisplayController.setActivated(activate); + mNightDisplayTintController.setActivated(activate); } updateNextAlarm(mNightDisplayTintController.isActivated(), now); @@ -1014,8 +1141,8 @@ public final class ColorDisplayService extends SystemService { intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getContext().registerReceiver(mTimeChangedReceiver, intentFilter); - mStartTime = mNightDisplayController.getCustomStartTime(); - mEndTime = mNightDisplayController.getCustomEndTime(); + mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime(); + mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime(); mLastActivatedTime = getNightDisplayLastActivatedTimeSetting(); @@ -1083,13 +1210,13 @@ public final class ColorDisplayService extends SystemService { // Maintain the existing activated state if within the current period. if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise) ^ mLastActivatedTime.isBefore(sunset))) { - activate = mNightDisplayController.isActivated(); + activate = isNightDisplayActivatedSetting(); } } if (mNightDisplayTintController.isActivatedStateNotSet() || ( mNightDisplayTintController.isActivated() != activate)) { - mNightDisplayController.setActivated(activate); + mNightDisplayTintController.setActivated(activate); } } @@ -1211,6 +1338,114 @@ public final class ColorDisplayService extends SystemService { public abstract int getLevel(); } + private final class NightDisplayTintController extends TintController { + + private float[] mMatrix = new float[16]; + private final float[] mColorTempCoefficients = new float[9]; + private Integer mColorTemp; + + /** + * Set coefficients based on whether the color matrix is linear or not. + */ + @Override + public void setUp(Context context, boolean needsLinear) { + final String[] coefficients = context.getResources().getStringArray(needsLinear + ? R.array.config_nightDisplayColorTemperatureCoefficients + : R.array.config_nightDisplayColorTemperatureCoefficientsNative); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); + } + } + + @Override + public void setMatrix(int cct) { + if (mMatrix.length != 16) { + Slog.d(TAG, "The display transformation matrix must be 4x4"); + return; + } + + Matrix.setIdentityM(mMatrix, 0); + + final float squareTemperature = cct * cct; + final float red = squareTemperature * mColorTempCoefficients[0] + + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2]; + final float green = squareTemperature * mColorTempCoefficients[3] + + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5]; + final float blue = squareTemperature * mColorTempCoefficients[6] + + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8]; + mMatrix[0] = red; + mMatrix[5] = green; + mMatrix[10] = blue; + } + + @Override + public float[] getMatrix() { + return isActivated() ? mMatrix : MATRIX_IDENTITY; + } + + @Override + public void setActivated(Boolean activated) { + if (activated == null) { + super.setActivated(null); + return; + } + + boolean activationStateChanged = activated != isActivated(); + + if (!isActivatedStateNotSet() && activationStateChanged) { + // This is a true state change, so set this as the last activation time. + Secure.putStringForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + LocalDateTime.now().toString(), + mCurrentUser); + } + + if (isActivatedStateNotSet() || activationStateChanged) { + super.setActivated(activated); + Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_ACTIVATED, + activated ? 1 : 0, mCurrentUser); + onActivated(activated); + } + } + + @Override + public int getLevel() { + return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; + } + + void onActivated(boolean activated) { + Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display"); + if (mNightDisplayAutoMode != null) { + mNightDisplayAutoMode.onActivated(activated); + } + + if (ColorDisplayManager.isDisplayWhiteBalanceAvailable(getContext())) { + updateDisplayWhiteBalanceStatus(); + } + + mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED); + } + + int getColorTemperature() { + return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp) + : getNightDisplayColorTemperatureSetting(); + } + + boolean setColorTemperature(int temperature) { + mColorTemp = temperature; + final boolean success = Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser); + onColorTemperatureChanged(temperature); + return success; + } + + void onColorTemperatureChanged(int temperature) { + setMatrix(temperature); + mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE); + } + } + /** * Local service that allows color transforms to be enabled from other system services. */ @@ -1270,7 +1505,7 @@ public final class ColorDisplayService extends SystemService { private final class TintHandler extends Handler { - TintHandler(Looper looper) { + private TintHandler(Looper looper) { super(looper, null, true /* async */); } @@ -1281,6 +1516,12 @@ public final class ColorDisplayService extends SystemService { mGlobalSaturationTintController.setMatrix(msg.arg1); applyTint(mGlobalSaturationTintController, false); break; + case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE: + applyTint(mNightDisplayTintController, true); + break; + case MSG_APPLY_NIGHT_DISPLAY_ANIMATED: + applyTint(mNightDisplayTintController, false); + break; } } } @@ -1290,11 +1531,37 @@ public final class ColorDisplayService extends SystemService { */ public interface ColorTransformController { - /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */ + /** + * Apply the given saturation (grayscale) matrix to the associated AppWindow. + */ void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation); } - private final class BinderService extends IColorDisplayManager.Stub { + @VisibleForTesting + final class BinderService extends IColorDisplayManager.Stub { + + @Override + public void setColorMode(int colorMode) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set display color mode"); + final long token = Binder.clearCallingIdentity(); + try { + setColorModeInternal(colorMode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getColorMode() { + final long token = Binder.clearCallingIdentity(); + try { + return getColorModeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } @Override public boolean isDeviceColorManaged() { @@ -1354,8 +1621,139 @@ public final class ColorDisplayService extends SystemService { } @Override + public boolean setNightDisplayActivated(boolean activated) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display activated"); + final long token = Binder.clearCallingIdentity(); + try { + mNightDisplayTintController.setActivated(activated); + return true; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean isNightDisplayActivated() { + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.isActivated(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayColorTemperature(int temperature) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display temperature"); + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.setColorTemperature(temperature); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayColorTemperature() { + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.getColorTemperature(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayAutoMode(int autoMode) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display auto mode"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayAutoModeInternal(autoMode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayAutoMode() { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to get night display auto mode"); + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayAutoModeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayAutoModeRaw() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayAutoModeRawInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayCustomStartTime(Time startTime) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display custom start time"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayCustomStartTimeInternal(startTime); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public Time getNightDisplayCustomStartTime() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayCustomStartTimeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayCustomEndTime(Time endTime) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display custom end time"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayCustomEndTimeInternal(endTime); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public Time getNightDisplayCustomEndTime() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayCustomEndTimeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { + return; + } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index cb3f91b7b6bb..b89768ac4b68 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.SensorManager; import android.hardware.display.AmbientBrightnessDayStats; @@ -93,9 +94,9 @@ import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.wm.SurfaceAnimationThread; import com.android.server.wm.WindowManagerInternal; -import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -294,6 +295,7 @@ public final class DisplayManagerService extends SystemService { // is rejected by the system. private final Curve mMinimumBrightnessCurve; private final Spline mMinimumBrightnessSpline; + private final ColorSpace mWideColorSpace; public DisplayManagerService(Context context) { this(context, new Injector()); @@ -322,6 +324,8 @@ public final class DisplayManagerService extends SystemService { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting(); mCurrentUserId = UserHandle.USER_SYSTEM; + ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces(); + mWideColorSpace = colorSpaces[1]; } public void setupSchedulerPolicies() { @@ -1073,6 +1077,10 @@ public final class DisplayManagerService extends SystemService { return mMinimumBrightnessCurve; } + int getPreferredWideGamutColorSpaceIdInternal() { + return mWideColorSpace.getId(); + } + private void setBrightnessConfigurationForUserInternal( @Nullable BrightnessConfiguration c, @UserIdInt int userId, @Nullable String packageName) { @@ -2128,6 +2136,16 @@ public final class DisplayManagerService extends SystemService { } } + @Override // Binder call + public int getPreferredWideGamutColorSpaceId() { + final long token = Binder.clearCallingIdentity(); + try { + return getPreferredWideGamutColorSpaceIdInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + void setBrightness(int brightness) { Settings.System.putIntForUser(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT); diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java index a5e9728e4b68..b1b7d3c8769b 100644 --- a/services/core/java/com/android/server/display/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/DisplayTransformManager.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.app.ActivityTaskManager; +import android.hardware.display.ColorDisplayManager; import android.opengl.Matrix; import android.os.IBinder; import android.os.Parcel; @@ -27,7 +28,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.ColorDisplayController; import java.util.Arrays; @@ -248,20 +248,20 @@ public class DisplayTransformManager { * work in linear space. */ public static boolean needsLinearColorMatrix(int colorMode) { - return colorMode != ColorDisplayController.COLOR_MODE_SATURATED; + return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED; } public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) { - if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) { + if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_MANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) { applySaturation(COLOR_SATURATION_BOOSTED); setDisplayColor(DISPLAY_COLOR_MANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_UNMANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_ENHANCED); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index eca371a78750..4db541c29448 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -288,6 +288,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final class DebugFlags { static final DebugFlag FLAG_OPTIMIZE_START_INPUT = new DebugFlag("debug.optimize_startinput", false); + static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS = + new DebugFlag("persist.pre_render_ime_views", false); } @UserIdInt @@ -304,6 +306,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final boolean mHasFeature; private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap = new ArrayMap<>(); + private final boolean mIsLowRam; private final HardKeyboardListener mHardKeyboardListener; private final AppOpsManager mAppOpsManager; private final UserManager mUserManager; @@ -403,6 +406,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final ClientDeathRecipient clientDeathRecipient; boolean sessionRequested; + // Determines if IMEs should be pre-rendered. + // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior + // through the life of the current client. + boolean shouldPreRenderIme; SessionState curSession; @Override @@ -615,6 +622,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * <dd> * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. * </dd> + * dt>{@link InputMethodService#IME_INVISIBLE}</dt> + * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is + * currently invisible. + * </dd> * </dl> * <em>Do not update this value outside of setImeWindowStatus.</em> */ @@ -1361,6 +1372,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); mHardKeyboardBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_externalHardKeyboardBehavior); + mIsLowRam = ActivityManager.isLowRamDeviceStatic(); Bundle extras = new Bundle(); extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true); @@ -1393,7 +1405,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( - mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady); + mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady); updateCurrentProfileIds(); AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); @@ -1682,7 +1694,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList); final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodListLocked(); } @@ -1738,7 +1750,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return Collections.emptyList(); } final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); } @@ -2264,7 +2276,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mSwitchingDialog != null) return false; if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false; - if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false; + if ((visibility & InputMethodService.IME_ACTIVE) == 0 + || (visibility & InputMethodService.IME_INVISIBLE) != 0) { + return false; + } if (mWindowManagerInternal.isHardKeyboardAvailable()) { if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) { // When physical keyboard is attached, we show the ime switcher (or notification if @@ -2372,6 +2387,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurToken == null) { return; } + if (DEBUG) { + Slog.d(TAG, "IME window vis: " + vis + + " active: " + (vis & InputMethodService.IME_ACTIVE) + + " inv: " + (vis & InputMethodService.IME_INVISIBLE)); + } + // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure // all updateSystemUi happens on system previlege. final long ident = Binder.clearCallingIdentity(); @@ -2830,6 +2851,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) { switchUserLocked(userId); } + // Master feature flag that overrides other conditions and forces IME preRendering. + if (DEBUG) { + Slog.v(TAG, "IME PreRendering MASTER flag: " + + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + + ", LowRam: " + mIsLowRam); + } + // pre-rendering not supported on low-ram devices. + cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam; if (mCurFocusedWindow == windowToken) { if (DEBUG) { @@ -3493,7 +3522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { setEnabledSessionInMainThread(session); session.method.startInput(startInputToken, inputContext, missingMethods, - editorInfo, restarting); + editorInfo, restarting, session.client.shouldPreRenderIme); } catch (RemoteException e) { } args.recycle(); @@ -4106,25 +4135,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mCurrentSubtype; } - @Override - public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { - synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { - return false; - } - if (subtype != null && mCurMethodId != null) { - InputMethodInfo imi = mMethodMap.get(mCurMethodId); - int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()); - if (subtypeId != NOT_A_SUBTYPE_ID) { - setInputMethodLocked(mCurMethodId, subtypeId); - return true; - } - } - return false; - } - } - private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { synchronized (mMethodMap) { return getInputMethodListLocked(userId); @@ -4440,6 +4450,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @ShellCommandResult private int refreshDebugProperties() { DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh(); + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh(); return ShellCommandResult.SUCCESS; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 88d1a9c0eb6c..326984c7202c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -34,6 +34,7 @@ import android.os.UserManagerInternal; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; @@ -66,9 +67,9 @@ import java.util.Locale; */ final class InputMethodUtils { public static final boolean DEBUG = false; - public static final int NOT_A_SUBTYPE_ID = -1; - public static final String SUBTYPE_MODE_ANY = null; - public static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + static final int NOT_A_SUBTYPE_ID = -1; + private static final String SUBTYPE_MODE_ANY = null; + static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String TAG = "InputMethodUtils"; private static final Locale ENGLISH_LOCALE = new Locale("en"); private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); @@ -108,7 +109,7 @@ final class InputMethodUtils { // ---------------------------------------------------------------------- // Utilities for debug - public static String getApiCallStack() { + static String getApiCallStack() { String apiCallStack = ""; try { throw new RuntimeException(); @@ -131,10 +132,9 @@ final class InputMethodUtils { } // ---------------------------------------------------------------------- - public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale requiredLocale, final boolean checkCountry, - final String requiredSubtypeMode) { + private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, + boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, + String requiredSubtypeMode) { if (!imi.isSystem()) { return false; } @@ -148,8 +148,8 @@ final class InputMethodUtils { } @Nullable - public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, - final Context context) { + private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, + Context context) { // At first, find the fallback locale from the IMEs that are declared as "default" in the // current locale. Note that IME developers can declare an IME as "default" only for // some particular locales but "not default" for other locales. @@ -177,8 +177,8 @@ final class InputMethodUtils { return null; } - private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute) { + private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, + Context context, boolean checkDefaultAttribute) { if (!imi.isSystem()) { return false; } @@ -198,7 +198,7 @@ final class InputMethodUtils { return false; } - public static Locale getSystemLocaleFromContext(final Context context) { + private static Locale getSystemLocaleFromContext(Context context) { try { return context.getResources().getConfiguration().locale; } catch (Resources.NotFoundException ex) { @@ -212,10 +212,9 @@ final class InputMethodUtils { @NonNull private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); - public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale locale, final boolean checkCountry, - final String requiredSubtypeMode) { + InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context, + boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, + String requiredSubtypeMode) { for (int i = 0; i < imis.size(); ++i) { final InputMethodInfo imi = imis.get(i); if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale, @@ -228,8 +227,7 @@ final class InputMethodUtils { // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be // documented more clearly. - public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis, - final Context context) { + InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { // If one or more auxiliary input methods are available, OK to stop populating the list. for (final InputMethodInfo imi : mInputMethodSet) { if (imi.isAuxiliaryIme()) { @@ -269,8 +267,8 @@ final class InputMethodUtils { } private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( - final ArrayList<InputMethodInfo> imis, final Context context, - @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) { + ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, + @Nullable Locale fallbackLocale) { // Once the system becomes ready, we pick up at least one keyboard in the following order. // Secondary users fall into this category in general. // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true @@ -317,7 +315,7 @@ final class InputMethodUtils { return builder; } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) { final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); // We will primarily rely on the system locale, but also keep relying on the fallback locale @@ -336,13 +334,13 @@ final class InputMethodUtils { return builder.build(); } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis) { return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); } - public static boolean containsSubtypeOf(final InputMethodInfo imi, - @Nullable final Locale locale, final boolean checkCountry, final String mode) { + static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, + boolean checkCountry, String mode) { if (locale == null) { return false; } @@ -371,7 +369,7 @@ final class InputMethodUtils { return false; } - public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -380,7 +378,7 @@ final class InputMethodUtils { return subtypes; } - public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { + static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { if (enabledImes == null || enabledImes.isEmpty()) { return null; } @@ -404,11 +402,11 @@ final class InputMethodUtils { return enabledImes.get(Math.max(firstFoundSystemIme, 0)); } - public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { + static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; } - public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { if (imi != null) { final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -430,7 +428,7 @@ final class InputMethodUtils { }; @VisibleForTesting - public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( + static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi) { final LocaleList systemLocales = res.getConfiguration().getLocales(); @@ -564,7 +562,7 @@ final class InputMethodUtils { * it will return the first subtype matched with mode * @return the most applicable subtypeId */ - public static InputMethodSubtype findLastResortApplicableSubtypeLocked( + static InputMethodSubtype findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort) { if (subtypes == null || subtypes.size() == 0) { @@ -615,14 +613,13 @@ final class InputMethodUtils { return applicableSubtype; } - public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { if (subtype == null) return true; return !subtype.isAuxiliary(); } - public static void setNonSelectedSystemImesDisabledUntilUsed( - IPackageManager packageManager, List<InputMethodInfo> enabledImis, - int userId, String callingPackage) { + static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, + List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) { if (DEBUG) { Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); } @@ -710,7 +707,7 @@ final class InputMethodUtils { } } - public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, + static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype) { final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); return subtype != null @@ -730,8 +727,8 @@ final class InputMethodUtils { * @param packageName the package name. * @return {@code true} if the package name belongs to the UID. */ - public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager, - final int uid, final String packageName) { + static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager, + @UserIdInt int uid, String packageName) { try { appOpsManager.checkPackage(uid, packageName); return true; @@ -760,6 +757,14 @@ final class InputMethodUtils { */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); + private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { + Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); + } + + private static final UserManagerInternal sUserManagerInternal = + LocalServices.getService(UserManagerInternal.class); + private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; @@ -802,10 +807,9 @@ final class InputMethodUtils { return imsList; } - public InputMethodSettings( - Resources res, ContentResolver resolver, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, - @UserIdInt int userId, boolean copyOnWrite) { + InputMethodSettings(Resources res, ContentResolver resolver, + ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, + boolean copyOnWrite) { mRes = res; mResolver = resolver; mMethodMap = methodMap; @@ -820,7 +824,7 @@ final class InputMethodUtils { * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual * settings on the {@link Settings.Secure} until we do the first write operation. */ - public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { + void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { if (DEBUG) { Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); } @@ -834,16 +838,18 @@ final class InputMethodUtils { // TODO: mCurrentProfileIds should be updated here. } - private void putString(@NonNull final String key, @Nullable final String str) { + private void putString(@NonNull String key, @Nullable String str) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putStringForUser(mResolver, key, str, userId); } } @Nullable - private String getString(@NonNull final String key, @Nullable final String defaultValue) { + private String getString(@NonNull String key, @Nullable String defaultValue) { final String result; if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { result = mCopyOnWriteDataStore.get(key); @@ -853,15 +859,17 @@ final class InputMethodUtils { return result != null ? result : defaultValue; } - private void putInt(final String key, final int value) { + private void putInt(String key, int value) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putIntForUser(mResolver, key, value, userId); } } - private int getInt(final String key, final int defaultValue) { + private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); return result != null ? Integer.parseInt(result) : 0; @@ -869,11 +877,11 @@ final class InputMethodUtils { return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); } - private void putBoolean(final String key, final boolean value) { + private void putBoolean(String key, boolean value) { putInt(key, value ? 1 : 0); } - private boolean getBoolean(final String key, final boolean defaultValue) { + private boolean getBoolean(String key, boolean defaultValue) { return getInt(key, defaultValue ? 1 : 0) == 1; } @@ -893,12 +901,12 @@ final class InputMethodUtils { } } - public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { + ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { return createEnabledInputMethodListLocked( getEnabledInputMethodsAndSubtypeListLocked()); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeListLocked(imi); @@ -909,8 +917,7 @@ final class InputMethodUtils { return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( - InputMethodInfo imi) { + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { List<Pair<String, ArrayList<String>>> imsList = getEnabledInputMethodsAndSubtypeListLocked(); ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); @@ -934,13 +941,13 @@ final class InputMethodUtils { return enabledSubtypes; } - public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), mInputMethodSplitter, mSubtypeSplitter); } - public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { + void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { if (reloadInputMethodStr) { getEnabledInputMethodsStr(); } @@ -957,7 +964,7 @@ final class InputMethodUtils { * Build and put a string of EnabledInputMethods with removing specified Id. * @return the specified id was removed or not. */ - public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { boolean isRemoved = false; boolean needsAppendSeparator = false; @@ -1012,7 +1019,7 @@ final class InputMethodUtils { } @NonNull - public String getEnabledInputMethodsStr() { + String getEnabledInputMethodsStr() { mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); if (DEBUG) { Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache @@ -1080,12 +1087,12 @@ final class InputMethodUtils { } } - public Pair<String, String> getLastInputMethodAndSubtypeLocked() { + Pair<String, String> getLastInputMethodAndSubtypeLocked() { // Gets the first one from the history return getLastSubtypeForInputMethodLockedInternal(null); } - public String getLastSubtypeForInputMethodLocked(String imeId) { + String getLastSubtypeForInputMethodLocked(String imeId) { Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); if (ime != null) { return ime.second; @@ -1203,7 +1210,7 @@ final class InputMethodUtils { return history; } - public void putSelectedInputMethod(String imeId) { + void putSelectedInputMethod(String imeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mCurrentUserId); @@ -1211,7 +1218,7 @@ final class InputMethodUtils { putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); } - public void putSelectedSubtype(int subtypeId) { + void putSelectedSubtype(int subtypeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mCurrentUserId); @@ -1220,7 +1227,7 @@ final class InputMethodUtils { } @Nullable - public String getSelectedInputMethod() { + String getSelectedInputMethod() { final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); if (DEBUG) { Slog.d(TAG, "getSelectedInputMethodStr: " + imi); @@ -1228,7 +1235,7 @@ final class InputMethodUtils { return imi; } - public boolean isSubtypeSelected() { + boolean isSubtypeSelected() { return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; } @@ -1236,11 +1243,11 @@ final class InputMethodUtils { return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); } - public boolean isShowImeWithHardKeyboardEnabled() { + boolean isShowImeWithHardKeyboardEnabled() { return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false); } - public void setShowImeWithHardKeyboard(boolean show) { + void setShowImeWithHardKeyboard(boolean show) { putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show); } @@ -1249,7 +1256,7 @@ final class InputMethodUtils { return mCurrentUserId; } - public int getSelectedInputMethodSubtypeId(String selectedImiId) { + int getSelectedInputMethodSubtypeId(String selectedImiId) { final InputMethodInfo imi = mMethodMap.get(selectedImiId); if (imi == null) { return NOT_A_SUBTYPE_ID; @@ -1258,8 +1265,8 @@ final class InputMethodUtils { return getSubtypeIdFromHashCode(imi, subtypeHashCode); } - public void saveCurrentInputMethodAndSubtypeToHistory( - String curMethodId, InputMethodSubtype currentSubtype) { + void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, + InputMethodSubtype currentSubtype) { String subtypeId = NOT_A_SUBTYPE_ID_STR; if (currentSubtype != null) { subtypeId = String.valueOf(currentSubtype.hashCode()); @@ -1277,8 +1284,8 @@ final class InputMethodUtils { } } - public static boolean isSoftInputModeStateVisibleAllowed( - int targetSdkVersion, @StartInputFlags int startInputFlags) { + static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion, + @StartInputFlags int startInputFlags) { if (targetSdkVersion < Build.VERSION_CODES.P) { // for compatibility. return true; diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index a31b3b4b3b5a..3222143fc89f 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1519,13 +1519,6 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { - reportNotSupported(); - return false; - } - - @BinderThread - @Override public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { reportNotSupported(); } diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java index 4d9b5f5d01c6..bec1947df228 100644 --- a/services/core/java/com/android/server/job/JobConcurrencyManager.java +++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java @@ -36,6 +36,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.StatLogger; import com.android.server.job.JobSchedulerService.Constants; +import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.StateController; @@ -148,14 +149,14 @@ class JobConcurrencyManager { Slog.d(TAG, "Interactive: " + interactive); } - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis(); if (interactive) { - mLastScreenOnRealtime = now; + mLastScreenOnRealtime = nowRealtime; mEffectiveInteractiveState = true; mHandler.removeCallbacks(mRampUpForScreenOff); } else { - mLastScreenOffRealtime = now; + mLastScreenOffRealtime = nowRealtime; // Set mEffectiveInteractiveState to false after the delay, when we may increase // the concurrency. @@ -232,38 +233,24 @@ class JobConcurrencyManager { private void updateMaxCountsLocked() { refreshSystemStateLocked(); - if (mEffectiveInteractiveState) { - // Screen on - switch (mLastMemoryTrimLevel) { - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_MODERATE; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_LOW; - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_CRITICAL; - break; - default: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_NORMAL; - break; - } - } else { - // Screen off - switch (mLastMemoryTrimLevel) { - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_MODERATE; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_LOW; - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_CRITICAL; - break; - default: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_NORMAL; - break; - } + final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState + ? mConstants.MAX_JOB_COUNTS_SCREEN_ON + : mConstants.MAX_JOB_COUNTS_SCREEN_OFF; + + + switch (mLastMemoryTrimLevel) { + case ProcessStats.ADJ_MEM_FACTOR_MODERATE: + mMaxJobCounts = jobCounts.moderate; + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + mMaxJobCounts = jobCounts.low; + break; + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + mMaxJobCounts = jobCounts.critical; + break; + default: + mMaxJobCounts = jobCounts.normal; + break; } } @@ -303,7 +290,7 @@ class JobConcurrencyManager { // Initialize the work variables and also count running jobs. mJobCountTracker.reset( - mMaxJobCounts.getTotalMax(), + mMaxJobCounts.getMaxTotal(), mMaxJobCounts.getMaxBg(), mMaxJobCounts.getMinBg()); @@ -482,10 +469,7 @@ class JobConcurrencyManager { } - public void dumpLocked(IndentingPrintWriter pw) { - final long now = System.currentTimeMillis(); - final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis(); - + public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); pw.increaseIndent(); @@ -522,19 +506,36 @@ class JobConcurrencyManager { } } - public void dumpProtoLocked(ProtoOutputStream proto) { - // TODO Implement it. + public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { + final long token = proto.start(tag); + + proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE, + mCurrentInteractiveState); + proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE, + mEffectiveInteractiveState); + + proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS, + nowRealtime - mLastScreenOnRealtime); + proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, + nowRealtime - mLastScreenOffRealtime); + + mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER); + + proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, + mLastMemoryTrimLevel); + + proto.end(token); } /** - * This class decides, taking into account {@link #mMaxJobCounts} and how many jos are running / + * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running / * pending, how many more job can start. * * Extracted for testing and logging. */ @VisibleForTesting static class JobCountTracker { - private int mConfigNumTotalMaxJobs; + private int mConfigNumMaxTotalJobs; private int mConfigNumMaxBgJobs; private int mConfigNumMinBgJobs; @@ -552,7 +553,7 @@ class JobConcurrencyManager { private int mNumActualMaxBgJobs; void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) { - mConfigNumTotalMaxJobs = numTotalMaxJobs; + mConfigNumMaxTotalJobs = numTotalMaxJobs; mConfigNumMaxBgJobs = numMaxBgJobs; mConfigNumMinBgJobs = numMinBgJobs; @@ -607,12 +608,12 @@ class JobConcurrencyManager { // However, if there are FG jobs already running, we have to adjust it. mNumReservedForBg = Math.min(reservedForBg, - mConfigNumTotalMaxJobs - mNumRunningFgJobs); + mConfigNumMaxTotalJobs - mNumRunningFgJobs); // Max FG is [total - [number needed for BG jobs]] // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG] final int maxFg = - mConfigNumTotalMaxJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); + mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); // The above maxFg is the theoretical max. If there are less FG jobs, the actual // max FG will be lower accordingly. @@ -623,7 +624,7 @@ class JobConcurrencyManager { // Max BG is [total - actual max FG], but cap at [config max BG]. final int maxBg = Math.min( mConfigNumMaxBgJobs, - mConfigNumTotalMaxJobs - mNumActualMaxFgJobs); + mConfigNumMaxTotalJobs - mNumActualMaxFgJobs); // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly. // This isn't needed for the logic to work, but this will give consistent output @@ -669,12 +670,13 @@ class JobConcurrencyManager { final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs; return String.format( "Config={tot=%d bg min/max=%d/%d}" - + " Running: %d / %d (%d)" + + " Running[FG/BG (total)]: %d / %d (%d)" + " Pending: %d / %d (%d)" + " Actual max: %d%s / %d%s (%d%s)" + + " Res BG: %d" + " Starting: %d / %d (%d)" + " Total: %d%s / %d%s (%d%s)", - mConfigNumTotalMaxJobs, + mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs, @@ -684,19 +686,37 @@ class JobConcurrencyManager { mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs, - mNumActualMaxFgJobs, (totalFg <= mConfigNumTotalMaxJobs) ? "" : "*", + mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*", mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*", mNumActualMaxFgJobs + mNumActualMaxBgJobs, - (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumTotalMaxJobs) + (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs) ? "" : "*", + mNumReservedForBg, + mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs, totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*", totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*", - totalFg + totalBg, (totalFg + totalBg <= mConfigNumTotalMaxJobs) ? "" : "*" + totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*" ); } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs); + proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs); + proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs); + + proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs); + proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs); + + proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs); + proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs); + + proto.end(token); + } } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index cc0ac9a7e0a9..7625aafd0907 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -366,14 +366,20 @@ public class JobSchedulerService extends com.android.server.SystemService } } - public int getTotalMax() { + /** Total number of jobs to run simultaneously. */ + public int getMaxTotal() { return mTotal.getValue(); } + /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */ public int getMaxBg() { return mMaxBg.getValue(); } + /** + * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any + * pending, rather than always running the TOTAL number of FG jobs. + */ public int getMinBg() { return mMinBg.getValue(); } @@ -384,10 +390,39 @@ public class JobSchedulerService extends com.android.server.SystemService mMinBg.dump(pw, prefix); } - public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) { - mTotal.dumpProto(proto, tagTotal); - mMaxBg.dumpProto(proto, tagBg); - mMinBg.dumpProto(proto, tagBg); + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS); + mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG); + mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG); + proto.end(token); + } + } + + /** {@link MaxJobCounts} for each memory trim level. */ + static class MaxJobCountsPerMemoryTrimLevel { + public final MaxJobCounts normal; + public final MaxJobCounts moderate; + public final MaxJobCounts low; + public final MaxJobCounts critical; + + MaxJobCountsPerMemoryTrimLevel( + MaxJobCounts normal, + MaxJobCounts moderate, MaxJobCounts low, + MaxJobCounts critical) { + this.normal = normal; + this.moderate = moderate; + this.low = low; + this.critical = critical; + } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL); + moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE); + low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW); + critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL); + proto.end(token); } } @@ -443,6 +478,16 @@ public class JobSchedulerService extends com.android.server.SystemService "qc_window_size_rare_ms"; private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = "qc_max_execution_time_ms"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + "qc_max_job_count_active"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + "qc_max_job_count_working"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + "qc_max_job_count_frequent"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + "qc_max_job_count_rare"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + "qc_max_count_per_allowed_time"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -479,6 +524,15 @@ public class JobSchedulerService extends com.android.server.SystemService 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 4 * 60 * 60 * 1000L; // 4 hours + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + 200; // 1200/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + 1200; // 600/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + 1800; // 225/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + 2400; // 100/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -527,45 +581,44 @@ public class JobSchedulerService extends com.android.server.SystemService float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR; // Max job counts for screen on / off, for each memory trim level. - final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts( - 8, "max_job_total_on_normal", - 6, "max_job_max_bg_on_normal", - 2, "max_job_min_bg_on_normal"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts( - 8, "max_job_total_on_moderate", - 4, "max_job_max_bg_on_moderate", - 2, "max_job_min_bg_on_moderate"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts( - 5, "max_job_total_on_low", - 1, "max_job_max_bg_on_low", - 1, "max_job_min_bg_on_low"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts( - 5, "max_job_total_on_critical", - 1, "max_job_max_bg_on_critical", - 1, "max_job_min_bg_on_critical"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts( - 10, "max_job_total_off_normal", - 6, "max_job_max_bg_off_normal", - 2, "max_job_min_bg_off_normal"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts( - 10, "max_job_total_off_moderate", - 4, "max_job_max_bg_off_moderate", - 2, "max_job_min_bg_off_moderate"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts( - 5, "max_job_total_off_low", - 1, "max_job_max_bg_off_low", - 1, "max_job_min_bg_off_low"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts( - 5, "max_job_total_off_critical", - 1, "max_job_max_bg_off_critical", - 1, "max_job_min_bg_off_critical"); + final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON = + new MaxJobCountsPerMemoryTrimLevel( + new MaxJobCounts( + 8, "max_job_total_on_normal", + 6, "max_job_max_bg_on_normal", + 2, "max_job_min_bg_on_normal"), + new MaxJobCounts( + 8, "max_job_total_on_moderate", + 4, "max_job_max_bg_on_moderate", + 2, "max_job_min_bg_on_moderate"), + new MaxJobCounts( + 5, "max_job_total_on_low", + 1, "max_job_max_bg_on_low", + 1, "max_job_min_bg_on_low"), + new MaxJobCounts( + 5, "max_job_total_on_critical", + 1, "max_job_max_bg_on_critical", + 1, "max_job_min_bg_on_critical")); + + final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF = + new MaxJobCountsPerMemoryTrimLevel( + new MaxJobCounts( + 10, "max_job_total_off_normal", + 6, "max_job_max_bg_off_normal", + 2, "max_job_min_bg_off_normal"), + new MaxJobCounts( + 10, "max_job_total_off_moderate", + 4, "max_job_max_bg_off_moderate", + 2, "max_job_min_bg_off_moderate"), + new MaxJobCounts( + 5, "max_job_total_off_low", + 1, "max_job_max_bg_off_low", + 1, "max_job_min_bg_off_low"), + new MaxJobCounts( + 5, "max_job_total_off_critical", + 1, "max_job_max_bg_off_critical", + 1, "max_job_min_bg_off_critical")); + /** Wait for this long after screen off before increasing the job concurrency. */ final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = @@ -682,6 +735,41 @@ public class JobSchedulerService extends com.android.server.SystemService public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS; + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE; + + /** + * The maximum number of jobs that can run within the past + * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { @@ -712,15 +800,15 @@ public class JobSchedulerService extends com.android.server.SystemService MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR, DEFAULT_MODERATE_USE_FACTOR); - MAX_JOB_COUNTS_ON_NORMAL.parse(mParser); - MAX_JOB_COUNTS_ON_MODERATE.parse(mParser); - MAX_JOB_COUNTS_ON_LOW.parse(mParser); - MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser); - MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser); - MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser); - MAX_JOB_COUNTS_OFF_LOW.parse(mParser); - MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser); MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT, DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT); @@ -767,6 +855,21 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); } void dump(IndentingPrintWriter pw) { @@ -782,15 +885,17 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); - MAX_JOB_COUNTS_ON_NORMAL.dump(pw, ""); - MAX_JOB_COUNTS_ON_MODERATE.dump(pw, ""); - MAX_JOB_COUNTS_ON_LOW.dump(pw, ""); - MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, ""); - MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, ""); - MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, ""); - MAX_JOB_COUNTS_OFF_LOW.dump(pw, ""); - MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, ""); + + SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, ""); pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println(); pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println(); @@ -823,6 +928,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println(); pw.decreaseIndent(); } @@ -838,7 +953,11 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); - // TODO Dump max job counts. + MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON); + MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF); + + SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto, + ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS); proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT); proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT); @@ -872,6 +991,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); proto.end(qcToken); proto.end(token); @@ -3282,8 +3411,10 @@ public class JobSchedulerService extends com.android.server.SystemService void dumpInternal(final IndentingPrintWriter pw, int filterUid) { final int filterUidFinal = UserHandle.getAppId(filterUid); + final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowUptime = sUptimeMillisClock.millis(); + final Predicate<JobStatus> predicate = (js) -> { return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal; @@ -3459,7 +3590,7 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); - mConcurrencyManager.dumpLocked(pw); + mConcurrencyManager.dumpLocked(pw, now, nowElapsed); pw.println(); pw.print("PersistStats: "); @@ -3471,6 +3602,7 @@ public class JobSchedulerService extends com.android.server.SystemService void dumpInternalProto(final FileDescriptor fd, int filterUid) { ProtoOutputStream proto = new ProtoOutputStream(fd); final int filterUidFinal = UserHandle.getAppId(filterUid); + final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowUptime = sUptimeMillisClock.millis(); final Predicate<JobStatus> predicate = (js) -> { @@ -3614,7 +3746,8 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock); proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive); } - mConcurrencyManager.dumpProtoLocked(proto); + mConcurrencyManager.dumpProtoLocked(proto, + JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed); } proto.flush(); diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index c16d1b4ecec5..5a0b991bc7de 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -230,10 +230,10 @@ public final class QuotaController extends StateController { @VisibleForTesting static class ExecutionStats { /** - * The time at which this record should be considered invalid, in the elapsed realtime - * timebase. + * The time after which this record should be considered invalid (out of date), in the + * elapsed realtime timebase. */ - public long invalidTimeElapsed; + public long expirationTimeElapsed; public long windowSizeMs; @@ -241,29 +241,45 @@ public final class QuotaController extends StateController { public long executionTimeInWindowMs; public int bgJobCountInWindow; - /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */ + /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */ public long executionTimeInMaxPeriodMs; public int bgJobCountInMaxPeriod; /** - * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals - * the quota. This is only valid if - * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or - * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}. + * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs} + * equals the quota. This is only valid if + * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or + * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}. */ public long quotaCutoffTimeElapsed; + /** + * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the + * elapsed realtime timebase. + */ + public long jobCountExpirationTimeElapsed; + + /** + * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}. + * It may contain a few stale entries since cleanup won't happen exactly every + * {@link #mAllowedTimePerPeriodMs}. + */ + public int jobCountInAllowedTime; + @Override public String toString() { return new StringBuilder() - .append("invalidTime=").append(invalidTimeElapsed).append(", ") + .append("expirationTime=").append(expirationTimeElapsed).append(", ") .append("windowSize=").append(windowSizeMs).append(", ") .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ") .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ") .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs) .append(", ") .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ") - .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed) + .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ") + .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed) + .append(", ") + .append("jobCountInAllowedTime").append(jobCountInAllowedTime) .toString(); } @@ -271,13 +287,15 @@ public final class QuotaController extends StateController { public boolean equals(Object obj) { if (obj instanceof ExecutionStats) { ExecutionStats other = (ExecutionStats) obj; - return this.invalidTimeElapsed == other.invalidTimeElapsed + return this.expirationTimeElapsed == other.expirationTimeElapsed && this.windowSizeMs == other.windowSizeMs && this.executionTimeInWindowMs == other.executionTimeInWindowMs && this.bgJobCountInWindow == other.bgJobCountInWindow && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod - && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed; + && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed + && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed + && this.jobCountInAllowedTime == other.jobCountInAllowedTime; } else { return false; } @@ -286,13 +304,15 @@ public final class QuotaController extends StateController { @Override public int hashCode() { int result = 0; - result = 31 * result + hashLong(invalidTimeElapsed); + result = 31 * result + hashLong(expirationTimeElapsed); result = 31 * result + hashLong(windowSizeMs); result = 31 * result + hashLong(executionTimeInWindowMs); result = 31 * result + bgJobCountInWindow; result = 31 * result + hashLong(executionTimeInMaxPeriodMs); result = 31 * result + bgJobCountInMaxPeriod; result = 31 * result + hashLong(quotaCutoffTimeElapsed); + result = 31 * result + hashLong(jobCountExpirationTimeElapsed); + result = 31 * result + jobCountInAllowedTime; return result; } } @@ -320,7 +340,7 @@ public final class QuotaController extends StateController { /** * List of jobs that started while the UID was in the TOP state. There will be no more than - * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is + * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is * fine. */ private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); @@ -343,7 +363,7 @@ public final class QuotaController extends StateController { private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; /** - * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS} + * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} * window. */ private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS; @@ -355,17 +375,20 @@ public final class QuotaController extends StateController { private long mQuotaBufferMs = 30 * 1000L; // 30 seconds /** - * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when - * an app will have enough quota to transition from out-of-quota to in-quota. + * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine + * when an app will have enough quota to transition from out-of-quota to in-quota. */ private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; /** - * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an + * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an * app will have enough quota to transition from out-of-quota to in-quota. */ private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */ + private int mMaxJobCountPerAllowedTime = 20; + private long mNextCleanupTimeElapsed = 0; private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = new AlarmManager.OnAlarmListener() { @@ -412,6 +435,23 @@ public final class QuotaController extends StateController { /** The maximum period any bucket can have. */ private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; + /** + * The maximum number of jobs based on its standby bucket. For each max value count in the + * array, the app will not be allowed to run more than that many number of jobs within the + * latest time interval of its rolling window size. + * + * @see #mBucketPeriodsMs + */ + private final int[] mMaxBucketJobCounts = new int[] { + 200, // ACTIVE -- 1200/hr + 1200, // WORKING -- 600/hr + 1800, // FREQUENT -- 225/hr + 2400 // RARE -- 100/hr + }; + + /** The minimum number of jobs that any bucket will be allowed to run. */ + private static final int MIN_BUCKET_JOB_COUNT = 100; + /** An app has reached its quota. The message should contain a {@link Package} object. */ private static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ @@ -463,17 +503,21 @@ public final class QuotaController extends StateController { @Override public void prepareForExecutionLocked(JobStatus jobStatus) { if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); + + final int uid = jobStatus.getSourceUid(); + if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) { + mTopStartedJobs.add(jobStatus); + // Top jobs won't count towards quota so there's no need to involve the Timer. + return; + } + final int userId = jobStatus.getSourceUserId(); final String packageName = jobStatus.getSourcePackageName(); - final int uid = jobStatus.getSourceUid(); Timer timer = mPkgTimers.get(userId, packageName); if (timer == null) { timer = new Timer(uid, userId, packageName); mPkgTimers.add(userId, packageName, timer); } - if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) { - mTopStartedJobs.add(jobStatus); - } timer.startTrackingJob(jobStatus); } @@ -548,6 +592,36 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; changed = true; } + int newMaxCountPerAllowedPeriod = Math.max(10, + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); + if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) { + mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod; + changed = true; + } + int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE)); + if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) { + mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount; + changed = true; + } + int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING)); + if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) { + mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount; + changed = true; + } + int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT)); + if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) { + mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount; + changed = true; + } + int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE)); + if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) { + mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; + changed = true; + } if (changed) { // Update job bookkeeping out of band. @@ -631,18 +705,39 @@ public final class QuotaController extends StateController { return isTopStartedJob(jobStatus) || isUidInForeground(jobStatus.getSourceUid()) || isWithinQuotaLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } - private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, + @VisibleForTesting + boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { if (standbyBucket == NEVER_INDEX) return false; // This check is needed in case the flag is toggled after a job has been registered. if (!mShouldThrottle) return true; // Quota constraint is not enforced while charging or when parole is on. - return mChargeTracker.isCharging() || mInParole - || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0; + if (mChargeTracker.isCharging() || mInParole) { + return true; + } + + return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0 + && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName, + final int standbyBucket) { + ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false); + return isUnderJobCountQuotaLocked(stats, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, + final int standbyBucket) { + final long now = sElapsedRealtimeClock.millis(); + final boolean isUnderAllowedTimeQuota = + (stats.jobCountExpirationTimeElapsed <= now + || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime); + return isUnderAllowedTimeQuota + && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]); } @VisibleForTesting @@ -679,6 +774,13 @@ public final class QuotaController extends StateController { @NonNull ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { + return getExecutionStatsLocked(userId, packageName, standbyBucket, true); + } + + @NonNull + private ExecutionStats getExecutionStatsLocked(final int userId, + @NonNull final String packageName, final int standbyBucket, + final boolean refreshStatsIfOld) { if (standbyBucket == NEVER_INDEX) { Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); return new ExecutionStats(); @@ -693,14 +795,16 @@ public final class QuotaController extends StateController { stats = new ExecutionStats(); appStats[standbyBucket] = stats; } - final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; - Timer timer = mPkgTimers.get(userId, packageName); - if ((timer != null && timer.isActive()) - || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis() - || stats.windowSizeMs != bucketWindowSizeMs) { - // The stats are no longer valid. - stats.windowSizeMs = bucketWindowSizeMs; - updateExecutionStatsLocked(userId, packageName, stats); + if (refreshStatsIfOld) { + final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; + Timer timer = mPkgTimers.get(userId, packageName); + if ((timer != null && timer.isActive()) + || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() + || stats.windowSizeMs != bucketWindowSizeMs) { + // The stats are no longer valid. + stats.windowSizeMs = bucketWindowSizeMs; + updateExecutionStatsLocked(userId, packageName, stats); + } } return stats; @@ -717,14 +821,14 @@ public final class QuotaController extends StateController { Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); - stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS; + stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { stats.executionTimeInWindowMs = stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); // If the timer is active, the value will be stale at the next method call, so // invalidate now. - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, nowElapsed - mAllowedTimeIntoQuotaMs); @@ -800,7 +904,7 @@ public final class QuotaController extends StateController { break; } } - stats.invalidTimeElapsed = nowElapsed + emptyTimeMs; + stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; } private void invalidateAllExecutionStatsLocked(final int userId, @@ -811,13 +915,35 @@ public final class QuotaController extends StateController { for (int i = 0; i < appStats.length; ++i) { ExecutionStats stats = appStats[i]; if (stats != null) { - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; } } } } @VisibleForTesting + void incrementJobCount(final int userId, @NonNull final String packageName, int count) { + final long now = sElapsedRealtimeClock.millis(); + ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); + if (appStats == null) { + appStats = new ExecutionStats[mBucketPeriodsMs.length]; + mExecutionStatsCache.add(userId, packageName, appStats); + } + for (int i = 0; i < appStats.length; ++i) { + ExecutionStats stats = appStats[i]; + if (stats == null) { + stats = new ExecutionStats(); + appStats[i] = stats; + } + if (stats.jobCountExpirationTimeElapsed <= now) { + stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs; + stats.jobCountInAllowedTime = 0; + } + stats.jobCountInAllowedTime += count; + } + } + + @VisibleForTesting void saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session) { synchronized (mLock) { @@ -1023,9 +1149,12 @@ public final class QuotaController extends StateController { final String pkgString = string(userId, packageName); ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); + QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs - && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) { + && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs + && isUnderJobCountQuota) { // Already in quota. Why was this method called? if (DEBUG) { Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString @@ -1042,18 +1171,22 @@ public final class QuotaController extends StateController { mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); return; } + if (alarmListener == null) { alarmListener = new QcAlarmListener(userId, packageName); mInQuotaAlarmListeners.add(userId, packageName, alarmListener); } // The time this app will have quota again. - long inQuotaTimeElapsed = - stats.quotaCutoffTimeElapsed + stats.windowSizeMs; + long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs; if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) { inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS); } + if (!isUnderJobCountQuota) { + inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, + stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs); + } // Only schedule the alarm if: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm (which could be the @@ -1228,6 +1361,7 @@ public final class QuotaController extends StateController { mRunningBgJobs.add(jobStatus); if (shouldTrackLocked()) { mBgJobCount++; + incrementJobCount(mPkg.userId, mPkg.packageName, 1); if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); @@ -1324,6 +1458,7 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. mBgJobCount = mRunningBgJobs.size(); + incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount); // Starting the timer means that all cached execution stats are now // incorrect. invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); @@ -1604,6 +1739,12 @@ public final class QuotaController extends StateController { @VisibleForTesting @NonNull + int[] getBucketMaxJobCounts() { + return mMaxBucketJobCounts; + } + + @VisibleForTesting + @NonNull long[] getBucketWindowSizes() { return mBucketPeriodsMs; } @@ -1631,6 +1772,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + int getMaxJobCountPerAllowedTime() { + return mMaxJobCountPerAllowedTime; + } + + @VisibleForTesting @Nullable List<TimingSession> getTimingSessions(int userId, String packageName) { return mTimingSessions.get(userId, packageName); diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java index f1de37188510..e6f0ed9d14b0 100644 --- a/services/core/java/com/android/server/location/GeocoderProxy.java +++ b/services/core/java/com/android/server/location/GeocoderProxy.java @@ -20,8 +20,6 @@ import android.content.Context; import android.location.Address; import android.location.GeocoderParams; import android.location.IGeocodeProvider; -import android.os.RemoteException; -import android.util.Log; import com.android.internal.os.BackgroundThread; import com.android.server.ServiceWatcher; @@ -68,35 +66,22 @@ public class GeocoderProxy { public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { - final String[] result = new String[]{"Service not Available"}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - try { - result[0] = provider.getFromLocation( - latitude, longitude, maxResults, params, addrs); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return result[0]; + return provider.getFromLocation(latitude, longitude, maxResults, params, addrs); + }, "Service not Available"); } public String getFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) { - final String[] result = new String[]{"Service not Available"}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - try { - result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude, - lowerLeftLongitude, upperRightLatitude, upperRightLongitude, - maxResults, params, addrs); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return result[0]; + return provider.getFromLocationName(locationName, lowerLeftLatitude, + lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, params, addrs); + }, "Service not Available"); } } diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java index 591889dde5de..ca9c0e030b4e 100644 --- a/services/core/java/com/android/server/location/GnssVisibilityControl.java +++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java @@ -19,9 +19,15 @@ package com.android.server.location; import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -55,6 +61,8 @@ class GnssVisibilityControl { private static final String LOCATION_PERMISSION_NAME = "android.permission.ACCESS_FINE_LOCATION"; + private static final String[] NO_LOCATION_ENABLED_PROXY_APPS = new String[0]; + // Wakelocks private static final String WAKELOCK_KEY = TAG; private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; @@ -66,13 +74,16 @@ class GnssVisibilityControl { private final Handler mHandler; private final Context mContext; + private boolean mIsMasterLocationSettingsEnabled = true; + private boolean mIsOnRoamingNetwork = false; + // Number of non-framework location access proxy apps is expected to be small (< 5). private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7; private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>( HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS); private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener = - uid -> postEvent(() -> handlePermissionsChanged(uid)); + uid -> runOnHandler(() -> handlePermissionsChanged(uid)); GnssVisibilityControl(Context context, Looper looper) { mContext = context; @@ -81,8 +92,15 @@ class GnssVisibilityControl { mHandler = new Handler(looper); mAppOps = mContext.getSystemService(AppOpsManager.class); mPackageManager = mContext.getPackageManager(); + + // Set to empty proxy app list initially until the configuration properties are loaded. + updateNfwLocationAccessProxyAppsInGnssHal(); + + // Listen for proxy app package installation, removal events. + listenForProxyAppsPackageUpdates(); + listenForRoamingNetworkUpdate(); + // TODO(b/122855984): Handle global location settings on/off. - // TODO(b/122856189): Handle roaming case. } void updateProxyApps(List<String> nfwLocationAccessProxyApps) { @@ -90,18 +108,68 @@ class GnssVisibilityControl { // but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling // so that the order of processing is preserved. GnssLocationProvider should // first load the new config parameters for the new SIM and then call this method. - postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps)); + runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps)); + } + + void masterLocationSettingsUpdated(boolean enabled) { + runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled)); } void reportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { - postEvent(() -> handleNfwNotification( + runOnHandler(() -> handleNfwNotification( new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode, isCachedLocation))); } - private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) { + private void listenForProxyAppsPackageUpdates() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + switch (action) { + case Intent.ACTION_PACKAGE_ADDED: + case Intent.ACTION_PACKAGE_REMOVED: + case Intent.ACTION_PACKAGE_REPLACED: + String pkgName = intent.getData().getEncodedSchemeSpecificPart(); + handleProxyAppPackageUpdate(pkgName, action); + break; + } + } + }, UserHandle.ALL, intentFilter, null, mHandler); + } + + private void handleProxyAppPackageUpdate(String pkgName, String action) { + final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName); + // pkgName is not one of the proxy apps in our list. + if (locationPermission == null) { + return; + } + + Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action); + final boolean updatedLocationPermission = hasLocationPermission(pkgName); + if (locationPermission != updatedLocationPermission) { + // Permission changed. So, update the GNSS HAL with the updated list. + mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission); + updateNfwLocationAccessProxyAppsInGnssHal(); + } + } + + private void handleUpdateProxyApps(List<String> nfwLocationAccessProxyApps) { + if (!isProxyAppListUpdated(nfwLocationAccessProxyApps)) { + return; + } + if (nfwLocationAccessProxyApps.isEmpty()) { // Stop listening for app permission changes. Clear the app list in GNSS HAL. if (!mProxyAppToLocationPermissions.isEmpty()) { @@ -125,6 +193,27 @@ class GnssVisibilityControl { updateNfwLocationAccessProxyAppsInGnssHal(); } + private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) { + if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) { + return true; + } + + for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) { + if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) { + return true; + } + } + + return false; + } + + private void handleMasterLocationSettingsUpdated(boolean enabled) { + mIsMasterLocationSettingsEnabled = enabled; + Log.i(TAG, "Master location settings switch changed to " + + (enabled ? "enabled" : "disabled")); + updateNfwLocationAccessProxyAppsInGnssHal(); + } + // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal private static class NfwNotification { private static final String KEY_PROTOCOL_STACK = "ProtocolStack"; @@ -149,7 +238,7 @@ class GnssVisibilityControl { private final boolean mInEmergencyMode; private final boolean mIsCachedLocation; - NfwNotification(String proxyAppPackageName, byte protocolStack, + private NfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { mProxyAppPackageName = proxyAppPackageName; @@ -162,7 +251,7 @@ class GnssVisibilityControl { mIsCachedLocation = isCachedLocation; } - void copyFieldsToIntent(Intent intent) { + private void copyFieldsToIntent(Intent intent) { intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack); if (!TextUtils.isEmpty(mOtherProtocolStackName)) { intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName); @@ -188,7 +277,7 @@ class GnssVisibilityControl { mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation); } - String getResponseTypeAsString() { + private String getResponseTypeAsString() { switch (mResponseType) { case NFW_RESPONSE_TYPE_REJECTED: return "REJECTED"; @@ -246,6 +335,24 @@ class GnssVisibilityControl { } private void updateNfwLocationAccessProxyAppsInGnssHal() { + final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess() + ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps(); + final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); + Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " + + proxyAppsStr); + boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps); + if (!result) { + Log.e(TAG, "Failed to update non-framework location access proxy apps in the" + + " GNSS HAL to: " + proxyAppsStr); + } + } + + private boolean shouldDisableNfwLocationAccess() { + // TODO(b/122856189): Add disableWhenRoaming configuration per proxy app. + return mIsOnRoamingNetwork || !mIsMasterLocationSettingsEnabled; + } + + private String[] getLocationPermissionEnabledProxyApps() { // Get a count of proxy apps with location permission enabled to array creation size. int countLocationPermissionEnabledProxyApps = 0; for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) { @@ -264,15 +371,7 @@ class GnssVisibilityControl { locationPermissionEnabledProxyApps[i++] = proxyApp; } } - - String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); - Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " - + proxyAppsStr); - boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps); - if (!result) { - Log.e(TAG, "Failed to update non-framework location access proxy apps in the" - + " GNSS HAL to: " + proxyAppsStr); - } + return locationPermissionEnabledProxyApps; } private void handleNfwNotification(NfwNotification nfwNotification) { @@ -360,7 +459,31 @@ class GnssVisibilityControl { isPermissionMismatched); } - private void postEvent(Runnable event) { + private void listenForRoamingNetworkUpdate() { + // Register for network capabilities changes to monitor roaming changes. + ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addCapability(NetworkCapabilities.TRANSPORT_CELLULAR); + NetworkRequest networkRequest = networkRequestBuilder.build(); + mConnMgr.registerNetworkCallback(networkRequest, + new ConnectivityManager.NetworkCallback() { + @Override + public void onCapabilitiesChanged(Network network, + NetworkCapabilities capabilities) { + boolean isRoaming = !capabilities.hasTransport( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + // No locking required for this test and set because the callback + // runs in mHandler thread. + if (mIsOnRoamingNetwork != isRoaming) { + mIsOnRoamingNetwork = isRoaming; + updateNfwLocationAccessProxyAppsInGnssHal(); + } + } + }, mHandler); + } + + private void runOnHandler(Runnable event) { // Hold a wake lock until this message is delivered. // Note that this assumes the message will not be removed from the queue before // it is handled (otherwise the wake lock would be leaked). diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index a6da8c5e7713..6b5b1bebd20f 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -127,20 +127,16 @@ public class LocationProviderProxy extends AbstractLocationProvider { return mServiceWatcher.start(); } - private void initializeService(IBinder binder) { + private void initializeService(IBinder binder) throws RemoteException { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - try { - service.setLocationProviderManager(mManager); + service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } + synchronized (mRequestLock) { + if (mRequest != null) { + service.setRequest(mRequest, mWorkSource); } - } catch (RemoteException e) { - Log.w(TAG, e); } } @@ -157,63 +153,44 @@ public class LocationProviderProxy extends AbstractLocationProvider { } mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.setRequest(request, source); - } catch (RemoteException e) { - Log.w(TAG, e); - } + service.setRequest(request, source); }); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(" service=" + mServiceWatcher); - mServiceWatcher.runOnBinder(binder -> { + mServiceWatcher.runOnBinderBlocking(binder -> { try { TransferPipe.dumpAsync(binder, fd, args); } catch (IOException | RemoteException e) { - pw.println(" failed to dump location provider: " + e); + pw.println(" failed to dump location provider"); } - }); + return null; + }, null); } @Override public int getStatus(Bundle extras) { - int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - status[0] = service.getStatus(extras); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return status[0]; + return service.getStatus(extras); + }, LocationProvider.TEMPORARILY_UNAVAILABLE); } @Override public long getStatusUpdateTime() { - long[] updateTime = new long[] {0L}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - updateTime[0] = service.getStatusUpdateTime(); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return updateTime[0]; + return service.getStatusUpdateTime(); + }, 0L); } @Override public void sendExtraCommand(String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.sendExtraCommand(command, extras); - } catch (RemoteException e) { - Log.w(TAG, e); - } + service.sendExtraCommand(command, extras); }); } } diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS index 92b4d5fea113..c2c95e6042de 100644 --- a/services/core/java/com/android/server/location/OWNERS +++ b/services/core/java/com/android/server/location/OWNERS @@ -1,6 +1,8 @@ +aadmal@google.com arthuri@google.com bduddie@google.com gomo@google.com sooniln@google.com weiwa@google.com wyattriley@google.com +yuhany@google.com diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 8734ceb614a9..a9ae74f67de7 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -947,6 +947,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword) { checkWritePermission(userId); + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } synchronized (mSeparateChallengeLock) { setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword); } @@ -1305,6 +1309,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setLockCredential(String credential, int type, String savedCredential, int requestedQuality, int userId) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature"); + } checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId); @@ -2906,6 +2914,10 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int requestedQuality, int userId) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } try { return LockSettingsService.this.setLockCredentialWithToken(credential, type, tokenHandle, token, requestedQuality, userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 07f23ce2231a..6163077e1acf 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + import static com.android.internal.widget.LockPatternUtils.stringToPattern; import android.app.ActivityManager; @@ -58,6 +59,18 @@ class LockSettingsShellCommand extends ShellCommand { mCurrentUserId = ActivityManager.getService().getCurrentUser().id; parseArgs(); + if (!mLockPatternUtils.hasSecureLockScreen()) { + switch (cmd) { + case COMMAND_HELP: + case COMMAND_GET_DISABLED: + case COMMAND_SET_DISABLED: + break; + default: + getErrPrintWriter().println( + "The device does not support lock screen - ignoring the command."); + return -1; + } + } if (!checkCredential()) { return -1; } diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 1541b1d520cd..dd26a29d55af 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -42,8 +42,6 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; -import android.media.MediaController2; -import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.ControllerLink; import android.media.session.IActiveSessionsListener; @@ -60,7 +58,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -1007,17 +1004,50 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { if (DEBUG) { Log.d(TAG, "Session2 is created " + sessionToken); } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } + if (uid != sessionToken.getUid()) { + throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + + " but actually=" + sessionToken.getUid()); + } + int userId = UserHandle.getUserId(uid); + List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId); + if (session2Tokens.contains(sessionToken)) { + if (DEBUG) { + Log.d(TAG, "notifySession2Created(): Ignoring already existing token " + + sessionToken); + } + return; + } + session2Tokens.add(sessionToken); + pushSession2TokensChangedLocked(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + if (DEBUG) { + Log.d(TAG, "Session2 is destroyed " + sessionToken); + } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } if (uid != sessionToken.getUid()) { throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + " but actually=" + sessionToken.getUid()); } - Controller2Callback callback = new Controller2Callback(sessionToken); - // Note: It's safe not to keep controller here because it wouldn't be GC'ed until - // it's closed. - // TODO: Keep controller as well for better readability - // because the GC behavior isn't straightforward. - MediaController2 controller = new MediaController2(mContext, sessionToken, - new HandlerExecutor(mHandler), callback); + int userId = UserHandle.getUserId(uid); + mSession2TokensPerUser.get(userId).remove(sessionToken); + pushSession2TokensChangedLocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -2114,30 +2144,4 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); } } - - private class Controller2Callback extends MediaController2.ControllerCallback { - private final Session2Token mToken; - - Controller2Callback(Session2Token token) { - mToken = token; - } - - @Override - public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).add(mToken); - pushSession2TokensChangedLocked(userId); - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).remove(mToken); - pushSession2TokensChangedLocked(userId); - } - } - } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 47a55971b9c7..de3f50ad8c2c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -845,10 +845,12 @@ public class NotificationManagerService extends SystemService { reportSeen(r); } r.setVisibility(true, nv.rank, nv.count); + boolean isHun = (nv.location + == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); // hasBeenVisiblyExpanded must be called after updating the expansion state of // the NotificationRecord to ensure the expansion state is up-to-date. - if (r.hasBeenVisiblyExpanded()) { - logSmartSuggestionsVisible(r); + if (isHun || r.hasBeenVisiblyExpanded()) { + logSmartSuggestionsVisible(r, nv.location.toMetricsEventEnum()); } maybeRecordInterruptionLocked(r); nv.recycle(); @@ -876,7 +878,7 @@ public class NotificationManagerService extends SystemService { // hasBeenVisiblyExpanded must be called after updating the expansion state of // the NotificationRecord to ensure the expansion state is up-to-date. if (r.hasBeenVisiblyExpanded()) { - logSmartSuggestionsVisible(r); + logSmartSuggestionsVisible(r, notificationLocation); } if (userAction) { MetricsLogger.action(r.getItemLogMaker() @@ -952,7 +954,7 @@ public class NotificationManagerService extends SystemService { }; @VisibleForTesting - void logSmartSuggestionsVisible(NotificationRecord r) { + void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) { // If the newly visible notification has smart suggestions // then log that the user has seen them. if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) @@ -966,7 +968,10 @@ public class NotificationManagerService extends SystemService { r.getNumSmartActionsAdded()) .addTaggedData( MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED, - r.getSuggestionsGeneratedByAssistant() ? 1 : 0); + r.getSuggestionsGeneratedByAssistant() ? 1 : 0) + // The fields in the NotificationVisibility.NotificationLocation enum map + // directly to the fields in the MetricsEvent.NotificationLocation enum. + .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation); mMetricsLogger.write(logMaker); } } @@ -1082,7 +1087,8 @@ public class NotificationManagerService extends SystemService { || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE) || action.equals(Intent.ACTION_PACKAGES_SUSPENDED) - || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) { + || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED) + || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); String pkgList[] = null; @@ -1103,6 +1109,23 @@ public class NotificationManagerService extends SystemService { uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); cancelNotifications = false; unhideNotifications = true; + } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { + final int distractionRestrictions = + intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS, + PackageManager.RESTRICTION_NONE); + if ((distractionRestrictions + & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + hideNotifications = true; + } else { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + unhideNotifications = true; + } + } else if (queryRestart) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)}; @@ -1646,6 +1669,7 @@ public class NotificationManagerService extends SystemService { IntentFilter suspendedPkgFilter = new IntentFilter(); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); + suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, suspendedPkgFilter, null, null); @@ -7738,6 +7762,20 @@ public class NotificationManagerService extends SystemService { mPackageIntentReceiver.onReceive(getContext(), intent); } + @VisibleForTesting + protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { + // only use for testing: mimic receive broadcast that package is (un)distracting + // but does not actually register that info with packagemanager + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); + + final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + /** * Wrapper for a StatusBarNotification object that allows transfer across a oneway * binder without sending large amounts of data over a oneway transaction. diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 02fc51f89e62..ac965faedd34 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -690,6 +690,8 @@ public final class NotificationRecord { importance)); } } + // We have now gotten all the information out of the adjustments and can forget them. + mAdjustments.clear(); } } diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 3d88f20f0710..2aaa1edcfad9 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -176,6 +176,14 @@ public class NotificationShellCmd extends ShellCommand { // only use for testing mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired()); } + case "distract_package": { + // only use for testing + // Flag values are in + // {@link android.content.pm.PackageManager.DistractionRestriction}. + mDirectService.simulatePackageDistractionBroadcast( + Integer.parseInt(getNextArgRequired()), + getNextArgRequired().split(",")); + } break; case "post": case "notify": diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java index e2415911e929..bee7a8b7166e 100644 --- a/services/core/java/com/android/server/os/BugreportManagerService.java +++ b/services/core/java/com/android/server/os/BugreportManagerService.java @@ -37,7 +37,6 @@ public class BugreportManagerService extends SystemService { @Override public void onStart() { mService = new BugreportManagerServiceImpl(getContext()); - // TODO(b/111441001): Needs sepolicy to be submitted first. - // publishBinderService(Context.BUGREPORT_SERVICE, mService); + publishBinderService(Context.BUGREPORT_SERVICE, mService); } } diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 1178cc18f828..f736056c5c7f 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -44,6 +44,7 @@ import java.io.FileDescriptor; */ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final String TAG = "BugreportManagerService"; + private static final String BUGREPORT_SERVICE = "bugreportd"; private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; private IDumpstate mDs = null; @@ -64,6 +65,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { throw new UnsupportedOperationException("setListener is not allowed on this service"); } + // TODO(b/111441001): Intercept onFinished here in system server and shutdown + // the bugreportd service. @Override @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(int callingUidUnused, String callingPackage, @@ -84,6 +87,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { bugreportFd, screenshotFd, bugreportMode, listener); } + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public void cancelBugreport() throws RemoteException { + // This tells init to cancel bugreportd service. + SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + mDs = null; + } + private boolean validate(@BugreportParams.BugreportMode int mode) { if (mode != BugreportParams.BUGREPORT_MODE_FULL && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE @@ -107,7 +118,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { */ private IDumpstate getDumpstateService() { // Start bugreport service. - SystemProperties.set("ctl.start", "bugreport"); + SystemProperties.set("ctl.start", BUGREPORT_SERVICE); IDumpstate ds = null; boolean timedOut = false; diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 65fc9824c76e..ad9ac1232437 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -50,6 +50,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; /** * {@hide} @@ -57,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class BackgroundDexOptService extends JobService { private static final String TAG = "BackgroundDexOptService"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int JOB_IDLE_OPTIMIZE = 800; private static final int JOB_POST_BOOT_UPDATE = 801; @@ -102,7 +103,6 @@ public class BackgroundDexOptService extends JobService { private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); private final File mDataDir = Environment.getDataDirectory(); - private static final long mDowngradeUnusedAppsThresholdInMillis = getDowngradeUnusedAppsThresholdInMillis(); @@ -275,21 +275,18 @@ public class BackgroundDexOptService extends JobService { long lowStorageThreshold = getLowStorageThreshold(context); // Optimize primary apks. - int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true, - sFailedPackageNamesPrimary); - + int result = optimizePackages(pm, pkgs, lowStorageThreshold, + /*isForPrimaryDex=*/ true); if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return result; } - - if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + if (supportSecondaryDex()) { result = reconcileSecondaryDexFiles(pm.getDexManager()); if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return result; } - - result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, - sFailedPackageNamesSecondary); + result = optimizePackages(pm, pkgs, lowStorageThreshold, + /*isForPrimaryDex=*/ false); } return result; } @@ -339,92 +336,84 @@ public class BackgroundDexOptService extends JobService { } private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, - long lowStorageThreshold, boolean is_for_primary_dex, - ArraySet<String> failedPackageNames) { + long lowStorageThreshold, boolean isForPrimaryDex) { ArraySet<String> updatedPackages = new ArraySet<>(); Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); + Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); // Only downgrade apps when space is low on device. // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean // up disk before user hits the actual lowStorageThreshold. final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold; boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); + Log.d(TAG, "Should Downgrade " + shouldDowngrade); + boolean dex_opt_performed = false; for (String pkg : pkgs) { int abort_code = abortIdleOptimizations(lowStorageThreshold); if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return abort_code; } - - synchronized (failedPackageNames) { - if (failedPackageNames.contains(pkg)) { - // Skip previously failing package - continue; - } - } - - int reason; - boolean downgrade; - long package_size_before = 0; //used when the app is downgraded // Downgrade unused packages. if (unusedPackages.contains(pkg) && shouldDowngrade) { - package_size_before = getPackageSize(pm, pkg); - // This applies for system apps or if packages location is not a directory, i.e. - // monolithic install. - if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) { - // For apps that don't have the oat directory, instead of downgrading, - // remove their compiler artifacts from dalvik cache. - pm.deleteOatArtifactsOfPackage(pkg); + dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex); + } else { + if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) { + // can't dexopt because of low space. continue; - } else { - reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; - downgrade = true; } - } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) { - reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; - downgrade = false; - } else { - // can't dexopt because of low space. - continue; + dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex); } - - synchronized (failedPackageNames) { - // Conservatively add package to the list of failing ones in case - // performDexOpt never returns. - failedPackageNames.add(pkg); + if (dex_opt_performed) { + updatedPackages.add(pkg); } + } - // Optimize package if needed. Note that there can be no race between - // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - boolean success; - int dexoptFlags = - DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | - DexoptOptions.DEXOPT_BOOT_COMPLETE | - (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) | - DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; - if (is_for_primary_dex) { - int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, - dexoptFlags)); - success = result != PackageDexOptimizer.DEX_OPT_FAILED; - if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { - updatedPackages.add(pkg); - } + notifyPinService(updatedPackages); + return OPTIMIZE_PROCESSED; + } + + + /** + * Try to downgrade the package to a smaller compilation filter. + * eg. if the package is in speed-profile the package will be downgraded to verify. + * @param pm PackageManagerService + * @param pkg The package to be downgraded. + * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. + * @return true if the package was downgraded. + */ + private boolean downgradePackage(PackageManagerService pm, String pkg, + boolean isForPrimaryDex) { + Log.d(TAG, "Downgrading " + pkg); + boolean dex_opt_performed = false; + int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; + int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE + | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB + | DexoptOptions.DEXOPT_DOWNGRADE; + long package_size_before = getPackageSize(pm, pkg); + + if (isForPrimaryDex) { + // This applies for system apps or if packages location is not a directory, i.e. + // monolithic install. + if (!pm.canHaveOatDir(pkg)) { + // For apps that don't have the oat directory, instead of downgrading, + // remove their compiler artifacts from dalvik cache. + pm.deleteOatArtifactsOfPackage(pkg); } else { - success = pm.performDexOpt(new DexoptOptions(pkg, - reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX)); - } - if (success) { - // Dexopt succeeded, remove package from the list of failing ones. - synchronized (failedPackageNames) { - failedPackageNames.remove(pkg); - } - if (downgrade) { - StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before, - getPackageSize(pm, pkg), /*aggressive=*/ false); - } + dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags); } + } else { + dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags); } - notifyPinService(updatedPackages); - return OPTIMIZE_PROCESSED; + + if (dex_opt_performed) { + StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before, + getPackageSize(pm, pkg), /*aggressive=*/ false); + } + return dex_opt_performed; + } + + private boolean supportSecondaryDex() { + return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); } private int reconcileSecondaryDexFiles(DexManager dm) { @@ -438,6 +427,73 @@ public class BackgroundDexOptService extends JobService { return OPTIMIZE_PROCESSED; } + /** + * + * Optimize package if needed. Note that there can be no race between + * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. + * @param pm An instance of PackageManagerService + * @param pkg The package to be downgraded. + * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. + * @return true if the package was downgraded. + */ + private boolean optimizePackage(PackageManagerService pm, String pkg, + boolean isForPrimaryDex) { + int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; + int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES + | DexoptOptions.DEXOPT_BOOT_COMPLETE + | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; + + return isForPrimaryDex + ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) + : performDexOptSecondary(pm, pkg, reason, dexoptFlags); + } + + private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason, + int dexoptFlags) { + int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, + () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags))); + return result == PackageDexOptimizer.DEX_OPT_PERFORMED; + } + + private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason, + int dexoptFlags) { + DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, + dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); + int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, + () -> pm.performDexOpt(dexoptOptions) + ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED + ); + return result == PackageDexOptimizer.DEX_OPT_PERFORMED; + } + + /** + * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails + * the package is added to the list of failed packages. + * Return one of following result: + * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} + * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} + * {@link PackageDexOptimizer#DEX_OPT_FAILED} + */ + private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex, + Supplier<Integer> performDexOptWrapper) { + ArraySet<String> sFailedPackageNames = + isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary; + synchronized (sFailedPackageNames) { + if (sFailedPackageNames.contains(pkg)) { + // Skip previously failing package + return PackageDexOptimizer.DEX_OPT_SKIPPED; + } + sFailedPackageNames.add(pkg); + } + int result = performDexOptWrapper.get(); + if (result != PackageDexOptimizer.DEX_OPT_FAILED) { + synchronized (sFailedPackageNames) { + sFailedPackageNames.remove(pkg); + } + } + return result; + } + // Evaluate whether or not idle optimizations should continue. private int abortIdleOptimizations(long lowStorageThreshold) { if (mAbortIdleOptimization.get()) { diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java index 2ae424dd4b1b..5b765dfee3a4 100644 --- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -22,63 +22,117 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Process; import android.os.ServiceManager; +import android.util.ByteStringUtils; +import android.util.EventLog; import android.util.Log; import com.android.server.pm.dex.DexLogger; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and - * charging. The actual logging is performed by {@link DexLogger}. + * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily + * while idle and charging and calls {@link DexLogger} to write dynamic code information to the + * event log. The audit watching job scans the event log periodically while idle to find AVC audit + * messages indicating use of dynamic native code and adds the information to {@link DexLogger}. * {@hide} */ public class DynamicCodeLoggingService extends JobService { private static final String TAG = DynamicCodeLoggingService.class.getName(); - private static final int JOB_ID = 2030028; - private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final boolean DEBUG = false; - private volatile boolean mStopRequested = false; + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; - private static final boolean DEBUG = false; + private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2); + + private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h + private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " "; + + private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN = + Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*" + + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*" + + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*" + + "\\btcontext=u:object_r:app_data_file:.*" + + "\\btclass=file\\b.*"); + + private volatile boolean mIdleLoggingStopRequested = false; + private volatile boolean mAuditWatchingStopRequested = false; /** - * Schedule our job with the {@link JobScheduler}. + * Schedule our jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { ComponentName serviceName = new ComponentName( "android", DynamicCodeLoggingService.class.getName()); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - js.schedule(new JobInfo.Builder(JOB_ID, serviceName) + js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName) .setRequiresDeviceIdle(true) .setRequiresCharging(true) - .setPeriodic(PERIOD_MILLIS) + .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS) .build()); + js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS) + .build()); + if (DEBUG) { - Log.d(TAG, "Job scheduled"); + Log.d(TAG, "Jobs scheduled"); } } @Override public boolean onStartJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStartJob"); + Log.d(TAG, "onStartJob " + jobId); + } + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = false; + new IdleLoggingThread(params).start(); + return true; // Job is running on another thread + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = false; + new AuditWatchingThread(params).start(); + return true; // Job is running on another thread + default: + // Shouldn't happen, but indicate nothing is running. + return false; } - mStopRequested = false; - new IdleLoggingThread(params).start(); - return true; // Job is running on another thread } @Override public boolean onStopJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStopJob"); + Log.d(TAG, "onStopJob " + jobId); } - mStopRequested = true; - return true; // Requests job be re-scheduled. + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = true; + return true; // Requests job be re-scheduled. + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = true; + return true; // Requests job be re-scheduled. + default: + return false; + } + } + + private static DexLogger getDexLogger() { + PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); + return pm.getDexManager().getDexLogger(); } private class IdleLoggingThread extends Thread { @@ -92,14 +146,13 @@ public class DynamicCodeLoggingService extends JobService { @Override public void run() { if (DEBUG) { - Log.d(TAG, "Starting logging run"); + Log.d(TAG, "Starting IdleLoggingJob run"); } - PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); - DexLogger dexLogger = pm.getDexManager().getDexLogger(); + DexLogger dexLogger = getDexLogger(); for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) { - if (mStopRequested) { - Log.w(TAG, "Stopping logging run at scheduler request"); + if (mIdleLoggingStopRequested) { + Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request"); return; } @@ -108,8 +161,128 @@ public class DynamicCodeLoggingService extends JobService { jobFinished(mParams, /* reschedule */ false); if (DEBUG) { - Log.d(TAG, "Finished logging run"); + Log.d(TAG, "Finished IdleLoggingJob run"); } } } + + private class AuditWatchingThread extends Thread { + private final JobParameters mParams; + + AuditWatchingThread(JobParameters params) { + super("DynamicCodeLoggingService_AuditWatchingJob"); + mParams = params; + } + + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "Starting AuditWatchingJob run"); + } + + if (processAuditEvents()) { + jobFinished(mParams, /* reschedule */ false); + if (DEBUG) { + Log.d(TAG, "Finished AuditWatchingJob run"); + } + } + } + + private boolean processAuditEvents() { + // Scan the event log for SELinux (avc) audit messages indicating when an + // (untrusted) app has executed native code from an app data + // file. Matches are recorded in DexLogger. + // + // These messages come from the kernel audit system via logd. (Note that + // some devices may not generate these messages at all, or the format may + // be different, in which case nothing will be recorded.) + // + // The messages use the auditd tag and the uid of the app that executed + // the code. + // + // A typical message might look like this: + // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable" + // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302 + // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768 + // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file + // + // The information we want is the uid and the path. (Note this may be + // either a quoted string, as shown above, or a sequence of hex-encoded + // bytes.) + // + // On each run we process all the matching events in the log. This may + // mean re-processing events we have already seen, and in any case there + // may be duplicate events for the same app+file. These are de-duplicated + // by DexLogger. + // + // Note that any app can write a message to the event log, including one + // that looks exactly like an AVC audit message, so the information may + // be spoofed by an app; in such a case the uid we see will be the app + // that generated the spoof message. + + try { + int[] tags = { EventLog.getTagCode("auditd") }; + if (tags[0] == -1) { + // auditd is not a registered tag on this system, so there can't be any messages + // of interest. + return true; + } + + DexLogger dexLogger = getDexLogger(); + + List<EventLog.Event> events = new ArrayList<>(); + EventLog.readEvents(tags, events); + + for (int i = 0; i < events.size(); ++i) { + if (mAuditWatchingStopRequested) { + Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request"); + return false; + } + + EventLog.Event event = events.get(i); + + // Discard clearly unrelated messages as quickly as we can. + int uid = event.getUid(); + if (!Process.isApplicationUid(uid)) { + continue; + } + Object data = event.getData(); + if (!(data instanceof String)) { + continue; + } + String message = (String) data; + if (!message.startsWith(AVC_PREFIX)) { + continue; + } + + // And then use a regular expression to verify it's one of the messages we're + // interested in and to extract the path of the file being loaded. + Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message); + if (!matcher.matches()) { + continue; + } + String path = matcher.group(1); + if (path == null) { + // If the path contains spaces or various weird characters the kernel + // hex-encodes the bytes; we need to undo that. + path = unhex(matcher.group(2)); + } + dexLogger.recordNative(uid, path); + } + + return true; + } catch (Exception e) { + Log.e(TAG, "AuditWatchingJob failed", e); + return true; + } + } + } + + private static String unhex(String hexEncodedPath) { + byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath); + if (bytes == null || bytes.length == 0) { + return ""; + } + return new String(bytes); + } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 8a6105cb7fa9..efafdfaf2b54 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -611,6 +611,31 @@ public class Installer extends SystemService { } } + public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) + throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.snapshotAppData(null, pkg, userId, storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, + String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId, + storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index a33f14bab4b1..d0ef4f1523d4 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -25,6 +25,7 @@ import android.app.AppGlobals; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -135,6 +136,7 @@ public class LauncherAppsService extends SystemService { private final Context mContext; private final UserManager mUm; private final UserManagerInternal mUserManagerInternal; + private final UsageStatsManagerInternal mUsageStatsManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final ShortcutServiceInternal mShortcutServiceInternal; @@ -156,6 +158,8 @@ public class LauncherAppsService extends SystemService { mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mUserManagerInternal = Preconditions.checkNotNull( LocalServices.getService(UserManagerInternal.class)); + mUsageStatsManagerInternal = Preconditions.checkNotNull( + LocalServices.getService(UsageStatsManagerInternal.class)); mActivityManagerInternal = Preconditions.checkNotNull( LocalServices.getService(ActivityManagerInternal.class)); mActivityTaskManagerInternal = Preconditions.checkNotNull( @@ -671,6 +675,30 @@ public class LauncherAppsService extends SystemService { } } + @Override + public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, + String packageName, UserHandle user) { + verifyCallingPackage(callingPackage); + if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) { + return null; + } + + final PackageManagerInternal pmi = + LocalServices.getService(PackageManagerInternal.class); + final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier()); + if (!cn.getPackageName().equals(callingPackage)) { + throw new SecurityException("Caller is not the active launcher"); + } + + final UsageStatsManagerInternal.AppUsageLimitData data = + mUsageStatsManagerInternal.getAppUsageLimit(packageName, user); + if (data == null) { + return null; + } + return new LauncherApps.AppUsageLimit( + data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining()); + } + private void ensureShortcutPermission(@NonNull String callingPackage) { verifyCallingPackage(callingPackage); if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(), diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS new file mode 100644 index 000000000000..60d792530101 --- /dev/null +++ b/services/core/java/com/android/server/pm/OWNERS @@ -0,0 +1,76 @@ +hackbod@android.com +hackbod@google.com +jsharkey@android.com +jsharkey@google.com +narayan@google.com +patb@google.com +svetoslavganov@android.com +svetoslavganov@google.com +toddke@android.com +toddke@google.com + +# dex +per-file AbstractStatsBase.java = agampe@google.com +per-file AbstractStatsBase.java = calin@google.com +per-file AbstractStatsBase.java = ngeoffray@google.com +per-file BackgroundDexOptService.java = agampe@google.com +per-file BackgroundDexOptService.java = calin@google.com +per-file BackgroundDexOptService.java = ngeoffray@google.com +per-file CompilerStats.java = agampe@google.com +per-file CompilerStats.java = calin@google.com +per-file CompilerStats.java = ngeoffray@google.com +per-file InstructionSets.java = agampe@google.com +per-file InstructionSets.java = calin@google.com +per-file InstructionSets.java = ngeoffray@google.com +per-file OtaDexoptService.java = agampe@google.com +per-file OtaDexoptService.java = calin@google.com +per-file OtaDexoptService.java = ngeoffray@google.com +per-file OtaDexoptShellCommand.java = agampe@google.com +per-file OtaDexoptShellCommand.java = calin@google.com +per-file OtaDexoptShellCommand.java = ngeoffray@google.com +per-file PackageDexOptimizer.java = agampe@google.com +per-file PackageDexOptimizer.java = calin@google.com +per-file PackageDexOptimizer.java = ngeoffray@google.com +per-file PackageManagerServiceCompilerMapping.java = agampe@google.com +per-file PackageManagerServiceCompilerMapping.java = calin@google.com +per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com +per-file PackageUsage.java = agampe@google.com +per-file PackageUsage.java = calin@google.com +per-file PackageUsage.java = ngeoffray@google.com + +# multi user / cross profile +per-file CrossProfileAppsServiceImpl.java = omakoto@google.com +per-file CrossProfileAppsServiceImpl.java = yamasani@google.com +per-file CrossProfileAppsService.java = omakoto@google.com +per-file CrossProfileAppsService.java = yamasani@google.com +per-file CrossProfileIntentFilter.java = omakoto@google.com +per-file CrossProfileIntentFilter.java = yamasani@google.com +per-file CrossProfileIntentResolver.java = omakoto@google.com +per-file CrossProfileIntentResolver.java = yamasani@google.com +per-file UserManagerService.java = omakoto@google.com +per-file UserManagerService.java = yamasani@google.com +per-file UserRestrictionsUtils.java = omakoto@google.com +per-file UserRestrictionsUtils.java = yamasani@google.com + +# security +per-file KeySetHandle.java = cbrubaker@google.com +per-file KeySetManagerService.java = cbrubaker@google.com +per-file PackageKeySetData.java = cbrubaker@google.com +per-file PackageSignatures.java = cbrubaker@google.com +per-file SELinuxMMAC.java = cbrubaker@google.com + +# shortcuts +per-file LauncherAppsService.java = omakoto@google.com +per-file ShareTargetInfo.java = omakoto@google.com +per-file ShortcutBitmapSaver.java = omakoto@google.com +per-file ShortcutDumpFiles.java = omakoto@google.com +per-file ShortcutLauncher.java = omakoto@google.com +per-file ShortcutNonPersistentUser.java = omakoto@google.com +per-file ShortcutPackage.java = omakoto@google.com +per-file ShortcutPackageInfo.java = omakoto@google.com +per-file ShortcutPackageItem.java = omakoto@google.com +per-file ShortcutParser.java = omakoto@google.com +per-file ShortcutRequestPinProcessor.java = omakoto@google.com +per-file ShortcutService.java = omakoto@google.com +per-file ShortcutUser.java = omakoto@google.com + diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 5412e9425adf..94b1b362f43a 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -506,8 +506,10 @@ public class PackageDexOptimizer { boolean isUsedByOtherApps) { int flags = info.flags; boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; - // When a priv app is configured to run out of box, only verify it. - if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) { + // When an app or priv app is configured to run out of box, only verify it. + if (info.isCodeIntegrityPreferred() + || (info.isPrivilegedApp() + && DexManager.isPackageSelectedToRunOob(info.packageName))) { return "verify"; } if (vmSafeMode) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 0ab2a7361ac0..eab5c8f866a8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -537,7 +537,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false, - false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR); + false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR, + ""); synchronized (mSessions) { mSessions.put(sessionId, session); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index b8825bbd2d72..494ec3ff67aa 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -155,6 +155,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_IS_FAILED = "isFailed"; private static final String ATTR_IS_APPLIED = "isApplied"; private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode"; + private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -267,6 +268,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private boolean mStagedSessionFailed; @GuardedBy("mLock") private int mStagedSessionErrorCode = SessionInfo.NO_ERROR; + @GuardedBy("mLock") + private String mStagedSessionErrorMessage; /** * Path to the validated base APK for this session, which may point at an @@ -413,7 +416,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, - boolean isFailed, boolean isApplied, int stagedSessionErrorCode) { + boolean isFailed, boolean isApplied, int stagedSessionErrorCode, + String stagedSessionErrorMessage) { mCallback = callback; mContext = context; mPm = pm; @@ -447,6 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; mStagedSessionErrorCode = stagedSessionErrorCode; + mStagedSessionErrorMessage = + stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; if (sealed) { synchronized (mLock) { try { @@ -499,7 +505,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isSessionApplied = mStagedSessionApplied; info.isSessionReady = mStagedSessionReady; info.isSessionFailed = mStagedSessionFailed; - info.setStagedSessionErrorCode(mStagedSessionErrorCode); + info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); } return info; } @@ -1971,17 +1977,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = false; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } /** {@hide} */ - void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) { + void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, + String errorMessage) { synchronized (mLock) { mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; + Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage); } mCallback.onStagedSessionChanged(this); } @@ -1993,6 +2003,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = true; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } @@ -2017,6 +2028,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return mStagedSessionErrorCode; } + /** {@hide} */ + String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + private void destroyInternal() { synchronized (mLock) { mSealed = true; @@ -2133,6 +2149,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed); writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied); writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode); + writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE, + mStagedSessionErrorMessage); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -2253,6 +2271,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED); final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE, SessionInfo.NO_ERROR); + final String stagedSessionErrorMessage = readStringAttribute(in, + ATTR_STAGED_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { throw new IllegalArgumentException("Can't restore staged session with invalid state."); @@ -2296,7 +2316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, - stagedSessionErrorCode); + stagedSessionErrorCode, stagedSessionErrorMessage); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 09fe26d19019..6eff8155dffa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRAD import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; +import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; @@ -201,6 +202,7 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; +import android.content.rollback.IRollbackManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -447,8 +449,7 @@ public class PackageManagerService extends IPackageManager.Stub private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60); - private static final boolean PRECOMPILED_LAYOUT_ENABLED = - SystemProperties.getBoolean("view.precompiled_layout_enabled", false); + private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts"; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -9117,7 +9118,7 @@ public class PackageManagerService extends IPackageManager.Stub pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; } - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { mArtManagerService.compileLayouts(pkg); } @@ -10572,8 +10573,6 @@ public class PackageManagerService extends IPackageManager.Stub Log.d(TAG, "Scanning package " + pkg.packageName); } - DexManager.maybeLogUnexpectedPackageDetails(pkg); - // Initialize package source and resource directories final File scanFile = new File(pkg.codePath); final File destCodeFile = new File(pkg.applicationInfo.getCodePath()); @@ -13872,6 +13871,38 @@ public class PackageManagerService extends IPackageManager.Stub } } + // If this is an update to a package that might be potentially downgraded, then we + // need to check with the rollback manager whether there's any userdata that might + // need to be restored for the package. + // + // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL. + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + final String packageName = res.pkg.applicationInfo.packageName; + final String seInfo = res.pkg.applicationInfo.seInfo; + final PackageSetting ps; + int appId = -1; + long ceDataInode = -1; + synchronized (mSettings) { + ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(userId); + } + } + + if (ps != null) { + try { + rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token); + } catch (RemoteException re) { + // Cannot happen, the RollbackManager is hosted in the same process. + } + doRestore = true; + } + } + if (!doRestore) { // No restore possible, or the Backup Manager was mysteriously not // available -- just fire the post-install work request directly. @@ -14569,6 +14600,9 @@ public class PackageManagerService extends IPackageManager.Stub enableRollbackIntent.putExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, installFlags); + enableRollbackIntent.putExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS, + resolveUserIds(args.user.getIdentifier())); enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), PACKAGE_MIME_TYPE); enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -16176,7 +16210,7 @@ public class PackageManagerService extends IPackageManager.Stub if (performDexopt) { // Compile the layout resources. - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); mViewCompiler.compileLayouts(pkg); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -20635,7 +20669,6 @@ public class PackageManagerService extends IPackageManager.Stub storage.registerListener(mStorageListener); mInstallerService.systemReady(); - mDexManager.systemReady(); mPackageDexOptimizer.systemReady(); getStorageManagerInternal().addExternalStoragePolicy( @@ -23791,6 +23824,11 @@ public class PackageManagerService extends IPackageManager.Stub } return mArtManagerService.compileLayouts(pkg); } + + @Override + public void finishPackageInstall(int token, boolean didLaunch) { + PackageManagerService.this.finishPackageInstall(token, didLaunch); + } } @GuardedBy("mPackages") diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 022c1aad8113..6f1eeeb7de7a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -39,11 +39,12 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; -import android.content.pm.PackageManagerInternal; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; @@ -258,6 +259,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHarmfulAppWarning(); case "get-harmful-app-warning": return runGetHarmfulAppWarning(); + case "get-stagedsessions": + return getStagedSessions(); case "uninstall-system-updates": return uninstallSystemUpdates(); default: { @@ -282,6 +285,28 @@ class PackageManagerShellCommand extends ShellCommand { return -1; } + private int getStagedSessions() { + final PrintWriter pw = getOutPrintWriter(); + try { + List<SessionInfo> stagedSessionsList = + mInterface.getPackageInstaller().getStagedSessions().getList(); + for (SessionInfo session: stagedSessionsList) { + pw.println("appPackageName = " + session.getAppPackageName() + + "; sessionId = " + session.getSessionId() + + "; isStaged = " + session.isStaged() + + "; isSessionReady = " + session.isSessionReady() + + "; isSessionApplied = " + session.isSessionApplied() + + "; isSessionFailed = " + session.isSessionFailed() + ";"); + } + } catch (RemoteException e) { + pw.println("Failure [" + + e.getClass().getName() + " - " + + e.getMessage() + "]"); + return 0; + } + return 1; + } + private int uninstallSystemUpdates() { final PrintWriter pw = getOutPrintWriter(); List<String> failedUninstalls = new LinkedList<>(); @@ -634,9 +659,9 @@ class PackageManagerShellCommand extends ShellCommand { if (showVersionCode) { pw.print(" versionCode:"); if (info.applicationInfo != null) { - pw.print(info.applicationInfo.versionCode); + pw.print(info.applicationInfo.longVersionCode); } else { - pw.print(info.versionCode); + pw.print(info.getLongVersionCode()); } } if (listInstaller && !isApex) { @@ -2307,7 +2332,7 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK; break; case "--apex": - sessionParams.installFlags |= PackageManager.INSTALL_APEX; + sessionParams.setInstallAsApex(); sessionParams.setStaged(); break; case "--multi-package": diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 563fd7f90c4b..84c8b606a9d9 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -690,6 +690,10 @@ class ShortcutPackage extends ShortcutPackageItem { return result; } + public boolean hasShareTargets() { + return !mShareTargets.isEmpty(); + } + /** * Return the filenames (excluding path names) of icon bitmap files from this package. */ diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index fdbaba24966b..792b34c16551 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2167,6 +2167,19 @@ public class ShortcutService extends IShortcutService.Stub { } } + @Override + public boolean hasShareTargets(String packageName, String packageToCheck, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + enforceSystem(); + + synchronized (mLock) { + throwIfUserLockedL(userId); + + return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets(); + } + } + @GuardedBy("mLock") private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 5311c2a55931..c4d27e5882c4 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -153,6 +153,19 @@ public class StagingManager { return success; } + private static boolean sendMarkStagedSessionReadyRequest(int sessionId) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } + private static boolean isApexSession(@NonNull PackageInstallerSession session) { return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; } @@ -166,6 +179,7 @@ public class StagingManager { if (!session.isMultiPackage() && isApexSession(session)) { success = submitSessionToApexService(session, null, apexInfoList); + } else if (session.isMultiPackage()) { List<PackageInstallerSession> childSessions = Arrays.stream(session.getChildSessionIds()) @@ -179,7 +193,13 @@ public class StagingManager { } // else this is a staged multi-package session with no APEX files. } - if (success && (apexInfoList.apexInfos.length > 0)) { + if (!success) { + session.setStagedSessionFailed( + SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more details."); + } + + if (apexInfoList.apexInfos.length > 0) { // For APEXes, we validate the signature here before we mark the session as ready, // so we fail the session early if there is a signature mismatch. For APKs, the // signature verification will be done by the package manager at the point at which @@ -190,16 +210,22 @@ public class StagingManager { for (ApexInfo apexPackage : apexInfoList.apexInfos) { if (!validateApexSignatureLocked(apexPackage.packagePath, apexPackage.packageName)) { - success = false; - break; + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APK-container signature verification failed for package " + + apexPackage.packageName + ". Signature of file " + + apexPackage.packagePath + " does not match the signature of " + + " the package already installed."); + // TODO(b/118865310): abort the session on apexd. + return; } } } - if (success) { - session.setStagedSessionReady(); - } else { - session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + session.setStagedSessionReady(); + if (!sendMarkStagedSessionReadyRequest(session.sessionId)) { + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more " + + "details."); } } @@ -217,13 +243,20 @@ public class StagingManager { return; } if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) { - session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED); + session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED, + "APEX activation failed. Check logcat messages from apexd for " + + "more information."); + } + if (apexSessionInfo.isVerified) { + // Session has been previously submitted to apexd, but didn't complete all the + // pre-reboot verification, perhaps because the device rebooted in the meantime. + // Greedily re-trigger the pre-reboot verification. + mBgHandler.post(() -> preRebootVerification(session)); } if (apexSessionInfo.isActivated) { session.setStagedSessionApplied(); // TODO(b/118865310) if multi-package proceed with the installation of APKs. } - // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ } // In every other case apexd will retry to apply the session at next boot. } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index aaa187468f8d..2455113d8874 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -16,6 +16,10 @@ package com.android.server.pm; +import com.google.android.collect.Sets; + +import com.android.internal.util.Preconditions; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -38,10 +42,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.util.Preconditions; - -import com.google.android.collect.Sets; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java index 78fa82c6bcdd..59cc0cfeef45 100644 --- a/services/core/java/com/android/server/pm/dex/DexLogger.java +++ b/services/core/java/com/android/server/pm/dex/DexLogger.java @@ -16,11 +16,15 @@ package com.android.server.pm.dex; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE; + import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.FileUtils; import android.os.RemoteException; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ByteStringUtils; import android.util.EventLog; @@ -35,20 +39,23 @@ import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.Set; /** - * This class is responsible for logging data about secondary dex files. - * The data logged includes hashes of the name and content of each file. + * This class is responsible for logging data about secondary dex files and, despite the name, + * native code executed from an app's private directory. The data logged includes hashes of the + * name and content of each file. */ public class DexLogger { private static final String TAG = "DexLogger"; - // Event log tag & subtag used for SafetyNet logging of dynamic - // code loading (DCL) - see b/63927552. + // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) - + // see b/63927552. private static final int SNET_TAG = 0x534e4554; - private static final String DCL_SUBTAG = "dcl"; + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; private final IPackageManager mPackageManager; private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; @@ -114,12 +121,11 @@ public class DexLogger { } int storageFlags; - if (appInfo.deviceProtectedDataDir != null - && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) { - storageFlags = StorageManager.FLAG_STORAGE_DE; - } else if (appInfo.credentialProtectedDataDir != null - && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) { + + if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) { storageFlags = StorageManager.FLAG_STORAGE_CE; + } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) { + storageFlags = StorageManager.FLAG_STORAGE_DE; } else { Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); @@ -139,6 +145,9 @@ public class DexLogger { + ": " + e.getMessage()); } + String subtag = fileInfo.mFileType == FILE_TYPE_DEX + ? DCL_DEX_SUBTAG + : DCL_NATIVE_SUBTAG; String fileName = new File(filePath).getName(); String message = PackageUtils.computeSha256Digest(fileName.getBytes()); @@ -165,7 +174,7 @@ public class DexLogger { } if (loadingUid != -1) { - writeDclEvent(loadingUid, message); + writeDclEvent(subtag, loadingUid, message); } } } @@ -175,21 +184,58 @@ public class DexLogger { } } + private boolean fileIsUnder(String filePath, String directoryPath) { + if (directoryPath == null) { + return false; + } + + try { + return FileUtils.contains(new File(directoryPath).getCanonicalPath(), + new File(filePath).getCanonicalPath()); + } catch (IOException e) { + return false; + } + } + @VisibleForTesting PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } @VisibleForTesting - void writeDclEvent(int uid, String message) { - EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message); + void writeDclEvent(String subtag, int uid, String message) { + EventLog.writeEvent(SNET_TAG, subtag, uid, message); } - void record(int loaderUserId, String dexPath, - String owningPackageName, String loadingPackageName) { + void recordDex(int loaderUserId, String dexPath, String owningPackageName, + String loadingPackageName) { if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, - PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, - loadingPackageName)) { + FILE_TYPE_DEX, loaderUserId, loadingPackageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + /** + * Record that an app running in the specified uid has executed native code from the file at + * {@link path}. + */ + public void recordNative(int loadingUid, String path) { + String[] packages; + try { + packages = mPackageManager.getPackagesForUid(loadingUid); + if (packages == null || packages.length == 0) { + return; + } + } catch (RemoteException e) { + // Can't happen, we're local. + return; + } + + String loadingPackageName = packages[0]; + int loadingUserId = UserHandle.getUserId(loadingUid); + + if (mPackageDynamicCodeLoading.record(loadingPackageName, path, + FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index b54683673e7b..7ac7395e1f99 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -16,31 +16,28 @@ package com.android.server.pm.dex; +import static android.provider.DeviceConfig.FsiBoot; + import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; -import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.content.pm.PackageParser; -import android.database.ContentObserver; -import android.os.Build; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; -import android.provider.Settings.Global; +import android.provider.DeviceConfig; import android.util.Log; import android.util.Slog; import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.server.pm.Installer; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageDexOptimizer; @@ -136,10 +133,6 @@ public class DexManager { return mDexLogger; } - public void systemReady() { - registerSettingObserver(); - } - /** * Notify about dex files loads. * Note that this method is invoked when apps load dex files and it should @@ -235,7 +228,7 @@ public class DexManager { continue; } - mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName, + mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName, loadingAppInfo.packageName); if (classLoaderContexts != null) { @@ -699,47 +692,10 @@ public class DexManager { mDexLogger.writeNow(); } - private void registerSettingObserver() { - final ContentResolver resolver = mContext.getContentResolver(); - - // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to - // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once - // it is done. - ContentObserver privAppOobObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0); - SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, - oobEnabled == 1 ? "true" : "false"); - } - }; - resolver.registerContentObserver( - Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver, - UserHandle.USER_SYSTEM); - // At boot, restore the value from the setting, which persists across reboot. - privAppOobObserver.onChange(true); - - ContentObserver privAppOobListObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST); - if (oobList == null) { - oobList = "ALL"; - } - SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList); - } - }; - resolver.registerContentObserver( - Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver, - UserHandle.USER_SYSTEM); - // At boot, restore the value from the setting, which persists across reboot. - privAppOobListObserver.onChange(true); - } - /** * Returns whether the given package is in the list of privilaged apps that should run out of - * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when - * the the OOB list is empty, all priv apps will run in OOB mode. + * box. This only makes sense if the feature is enabled. Note that when the the OOB list is + * empty, all priv apps will run in OOB mode. */ public static boolean isPackageSelectedToRunOob(String packageName) { return isPackageSelectedToRunOob(Arrays.asList(packageName)); @@ -747,19 +703,35 @@ public class DexManager { /** * Returns whether any of the given packages are in the list of privilaged apps that should run - * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that - * when the the OOB list is empty, all priv apps will run in OOB mode. + * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list + * is empty, all priv apps will run in OOB mode. */ public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) { - if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) { + return isPackageSelectedToRunOobInternal( + SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false), + SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"), + DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_ENABLED), + DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_WHITELIST), + packageNamesInSameProcess); + } + + @VisibleForTesting + /* package */ static boolean isPackageSelectedToRunOobInternal( + boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled, + String overrideWhitelist, Collection<String> packageNamesInSameProcess) { + // Allow experiment (if exists) to override device configuration. + boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true") + : isDefaultEnabled; + if (!enabled) { return false; } - String oobListProperty = SystemProperties.get( - PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"); - if ("ALL".equals(oobListProperty)) { + + // Similarly, experiment flag can override the whitelist. + String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist; + if ("ALL".equals(whitelist)) { return true; } - for (String oobPkgName : oobListProperty.split(",")) { + for (String oobPkgName : whitelist.split(",")) { if (packageNamesInSameProcess.contains(oobPkgName)) { return true; } @@ -768,32 +740,6 @@ public class DexManager { } /** - * Generates package related log if the package has code stored in unexpected way. - */ - public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) { - if (!Build.IS_DEBUGGABLE) { - return; - } - - if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) { - logIfPackageHasUncompressedCode(pkg); - } - } - - /** - * Generates log if the APKs in the given package have uncompressed dex file and so - * files that can be direclty mapped. - */ - private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) { - auditUncompressedCodeInApk(pkg.baseCodePath); - if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { - for (int i = 0; i < pkg.splitCodePaths.length; i++) { - auditUncompressedCodeInApk(pkg.splitCodePaths[i]); - } - } - } - - /** * Generates log if the archive located at {@code fileName} has uncompressed dex file and so * files that can be direclty mapped. */ diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS new file mode 100644 index 000000000000..fcc1f6c10eac --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/OWNERS @@ -0,0 +1,4 @@ +agampe@google.com +calin@google.com +ngeoffray@google.com +sehr@google.com diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java index 6d4bc8291611..cc26c9b5f76c 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -53,6 +53,9 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { // is represented in the text file format.) static final int FILE_TYPE_DEX = 'D'; + // Type code to indicate a secondary file containing native code. + static final int FILE_TYPE_NATIVE = 'N'; + private static final String TAG = "PackageDynamicCodeLoading"; private static final String FILE_VERSION_HEADER = "DCL1"; @@ -107,7 +110,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { - if (fileType != FILE_TYPE_DEX) { + if (!isValidFileType(fileType)) { throw new IllegalArgumentException("Bad file type: " + fileType); } synchronized (mLock) { @@ -120,6 +123,10 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { } } + private static boolean isValidFileType(int fileType) { + return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE; + } + /** * Return all packages that contain records of secondary dex files. (Note that data updates * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed @@ -407,7 +414,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { if (packages.length == 0) { throw new IOException("Malformed line: " + line); } - if (type != FILE_TYPE_DEX) { + if (!isValidFileType(type)) { throw new IOException("Unknown file type: " + line); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index d5af31300bf5..20d6d4e2ca79 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1290,6 +1290,7 @@ public final class DefaultPermissionGrantPolicy { return mContext.getPackageManager().getPackageInfo(pkg, DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags); } catch (NameNotFoundException e) { + Slog.e(TAG, "PackageNot found: " + pkg, e); return null; } } diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index 88b97ea2cb49..01dc01efaca8 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,4 +1,4 @@ -per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com +moltmann@google.com per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 41cab2d7ebd3..13c4d886e7b1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1901,9 +1901,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { return handleStartTransitionForKeyguardLw(transit, duration); } @@ -2570,6 +2569,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = { + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, }; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index e1a911e8ada5..1d829707f180 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -825,16 +825,16 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { // like the ANR / app crashed dialogs return canAddInternalSystemWindow ? 11 : 10; case TYPE_APPLICATION_OVERLAY: - return 12; + return canAddInternalSystemWindow ? 13 : 12; case TYPE_DREAM: // used for Dreams (screensavers with TYPE_DREAM windows) - return 13; + return 14; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. - return 14; + return 15; case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. - return 15; + return 16; case TYPE_STATUS_BAR: return 17; case TYPE_STATUS_BAR_PANEL: diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java index 055c941f8b0a..7f2dedb70514 100644 --- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java +++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java @@ -18,6 +18,7 @@ package com.android.server.policy.role; import android.annotation.NonNull; import android.app.role.RoleManager; +import android.content.ComponentName; import android.content.Context; import android.os.Debug; import android.provider.Settings; @@ -90,6 +91,17 @@ public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHolder return CollectionUtils.singletonOrEmpty(result); } + case RoleManager.ROLE_ASSISTANT: { + String legacyAssistant = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId); + + if (legacyAssistant == null || legacyAssistant.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.singletonList( + ComponentName.unflattenFromString(legacyAssistant).getPackageName()); + } + } default: { Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName); return Collections.emptyList(); diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java new file mode 100644 index 000000000000..a2c8dace9510 --- /dev/null +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.attention.AttentionManagerInternal; +import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.io.PrintWriter; + +/** + * Class responsible for checking if the user is currently paying attention to the phone and + * notifying {@link PowerManagerService} that user activity should be renewed. + * + * This class also implements a limit of how long the extension should be, to avoid security + * issues where the device would never be locked. + */ +public class AttentionDetector { + + private static final String TAG = "AttentionDetector"; + private static final boolean DEBUG = false; + + /** + * Invoked whenever user attention is detected. + */ + private final Runnable mOnUserAttention; + + /** + * The maximum time, in millis, that the phone can stay unlocked because of attention events, + * triggered by any user. + */ + @VisibleForTesting + protected long mMaximumExtensionMillis; + + private final Object mLock; + + /** + * {@link android.service.attention.AttentionService} API timeout. + */ + private long mMaxAttentionApiTimeoutMillis; + + /** + * Last known user activity. + */ + private long mLastUserActivityTime; + + @VisibleForTesting + protected AttentionManagerInternal mAttentionManager; + + /** + * If we're currently waiting for an attention callback + */ + private boolean mRequested; + + /** + * Current wakefulness of the device. {@see PowerManagerInternal} + */ + private int mWakefulness; + + @VisibleForTesting + final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() { + + @Override + public void onSuccess(int requestCode, int result, long timestamp) { + Slog.v(TAG, "onSuccess: " + requestCode + ", " + result + + " - current requestCode: " + getRequestCode()); + synchronized (mLock) { + if (requestCode == getRequestCode() && mRequested) { + mRequested = false; + if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + if (DEBUG) Slog.d(TAG, "Device slept before receiving callback."); + return; + } + if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) { + mOnUserAttention.run(); + } + } + } + } + + @Override + public void onFailure(int requestCode, int error) { + Slog.i(TAG, "Failed to check attention: " + error); + synchronized (mLock) { + if (requestCode == getRequestCode()) { + mRequested = false; + } + } + } + }; + + public AttentionDetector(Runnable onUserAttention, Object lock) { + mOnUserAttention = onUserAttention; + mLock = lock; + } + + public void systemReady(Context context) { + mAttentionManager = LocalServices.getService(AttentionManagerInternal.class); + mMaximumExtensionMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionMaximumExtension); + mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionApiTimeout); + } + + public long updateUserActivity(long nextScreenDimming) { + if (!isAttentionServiceSupported()) { + return nextScreenDimming; + } + + final long now = SystemClock.uptimeMillis(); + final long whenToCheck = nextScreenDimming - getAttentionTimeout(); + final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis; + if (now < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now)); + } + return nextScreenDimming; + } else if (whenToStopExtending < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Let device sleep to avoid false results and improve security " + + (whenToCheck - whenToStopExtending)); + } + return nextScreenDimming; + } else if (mRequested) { + if (DEBUG) { + Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode()); + } + return whenToCheck; + } + + // Ideally we should attribute mRequested to the result of #checkAttention, but the + // callback might arrive before #checkAttention returns (if there are cached results.) + // This means that we must assume that the request was successful, and then cancel it + // afterwards if AttentionManager couldn't deliver it. + mRequested = true; + final boolean sent = mAttentionManager.checkAttention(getRequestCode(), + getAttentionTimeout(), mCallback); + if (!sent) { + mRequested = false; + } + + Slog.v(TAG, "Checking user attention with request code: " + getRequestCode()); + return whenToCheck; + } + + /** + * Handles user activity by cancelling any pending attention requests and keeping track of when + * the activity happened. + * + * @param eventTime Activity time, in uptime millis. + * @param event Activity type as defined in {@link PowerManager}. + * @return 0 when activity was ignored, 1 when handled, -1 when invalid. + */ + public int onUserActivity(long eventTime, int event) { + switch (event) { + case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + return 0; + case PowerManager.USER_ACTIVITY_EVENT_OTHER: + case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: + cancelCurrentRequestIfAny(); + mLastUserActivityTime = eventTime; + return 1; + default: + if (DEBUG) { + Slog.d(TAG, "Attention not reset. Unknown activity event: " + event); + } + return -1; + } + } + + public void onWakefulnessChangeStarted(int wakefulness) { + mWakefulness = wakefulness; + if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + cancelCurrentRequestIfAny(); + } + } + + private void cancelCurrentRequestIfAny() { + if (mRequested) { + mAttentionManager.cancelAttentionCheck(getRequestCode()); + mRequested = false; + } + } + + @VisibleForTesting + int getRequestCode() { + return (int) (mLastUserActivityTime % Integer.MAX_VALUE); + } + + @VisibleForTesting + long getAttentionTimeout() { + return mMaxAttentionApiTimeoutMillis; + } + + /** + * {@see AttentionManagerInternal#isAttentionServiceSupported} + */ + @VisibleForTesting + boolean isAttentionServiceSupported() { + return mAttentionManager.isAttentionServiceSupported(); + } + + public void dump(PrintWriter pw) { + pw.print("AttentionDetector:"); + pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis); + pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis); + pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime); + pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported()); + pw.print(" mRequested=" + mRequested); + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index a02787308246..3be64802c9fc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -229,6 +229,7 @@ public final class PowerManagerService extends SystemService private final BatterySaverController mBatterySaverController; private final BatterySaverStateMachine mBatterySaverStateMachine; private final BatterySavingStats mBatterySavingStats; + private final AttentionDetector mAttentionDetector; private final BinderService mBinderService; private final LocalService mLocalService; private final NativeWrapper mNativeWrapper; @@ -736,6 +737,7 @@ public final class PowerManagerService extends SystemService mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mBatterySavingStats = new BatterySavingStats(mLock); mBatterySaverPolicy = @@ -804,6 +806,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); + mAttentionDetector.systemReady(mContext); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -1326,6 +1329,16 @@ public final class PowerManagerService extends SystemService } } + private void onUserAttention() { + synchronized (mLock) { + if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */, + Process.SYSTEM_UID)) { + updatePowerStateLocked(); + } + } + } + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime @@ -1346,6 +1359,7 @@ public final class PowerManagerService extends SystemService } mNotifier.onUserActivity(event, uid); + mAttentionDetector.onUserActivity(eventTime, event); if (mUserInactiveOverrideFromWindowManager) { mUserInactiveOverrideFromWindowManager = false; @@ -1593,6 +1607,7 @@ public final class PowerManagerService extends SystemService if (mNotifier != null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason); } + mAttentionDetector.onWakefulnessChangeStarted(wakefulness); } } @@ -2085,6 +2100,10 @@ public final class PowerManagerService extends SystemService nextTimeout = -1; } + if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) { + nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout); + } + if (nextProfileTimeout > 0) { nextTimeout = Math.min(nextTimeout, nextProfileTimeout); } @@ -3477,6 +3496,7 @@ public final class PowerManagerService extends SystemService mBatterySaverPolicy.dump(pw); mBatterySaverStateMachine.dump(pw); + mAttentionDetector.dump(pw); pw.println(); final int numProfiles = mProfilePowerState.size(); diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index 1d74350de978..ab2807a9a8a8 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -104,6 +104,7 @@ public class BatterySaverController implements BatterySaverPolicyListener { public static final int REASON_SETTING_CHANGED = 8; public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON = 9; public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF = 10; + public static final int REASON_STICKY_RESTORE_OFF = 13; /** * Plugin interface. All methods are guaranteed to be called on the same (handler) thread. diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java index b7f28da499e9..404e450ccf14 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java @@ -28,7 +28,6 @@ import android.os.Handler; import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Global; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -85,42 +84,56 @@ public class BatterySaverStateMachine { @GuardedBy("mLock") private boolean mIsBatteryLevelLow; - /** Previously known value of Global.LOW_POWER_MODE. */ + /** Previously known value of Settings.Global.LOW_POWER_MODE. */ @GuardedBy("mLock") private boolean mSettingBatterySaverEnabled; - /** Previously known value of Global.LOW_POWER_MODE_STICKY. */ + /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */ @GuardedBy("mLock") private boolean mSettingBatterySaverEnabledSticky; /** Config flag to track if battery saver's sticky behaviour is disabled. */ private final boolean mBatterySaverStickyBehaviourDisabled; + /** + * Whether or not to end sticky battery saver upon reaching a level specified by + * {@link #mSettingBatterySaverStickyAutoDisableThreshold}. + */ + @GuardedBy("mLock") + private boolean mSettingBatterySaverStickyAutoDisableEnabled; + + /** + * The battery level at which to end sticky battery saver. Only useful if + * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}. + */ + @GuardedBy("mLock") + private int mSettingBatterySaverStickyAutoDisableThreshold; + /** Config flag to track default disable threshold for Dynamic Power Savings enabled battery * saver. */ @GuardedBy("mLock") private final int mDynamicPowerSavingsDefaultDisableThreshold; /** - * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. + * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL. * (Currently only used in dumpsys.) */ @GuardedBy("mLock") private int mSettingBatterySaverTriggerThreshold; - /** Previously known value of Global.AUTOMATIC_POWER_SAVER_MODE. */ + /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVER_MODE. */ @GuardedBy("mLock") private int mSettingAutomaticBatterySaver; /** When to disable battery saver again if it was enabled due to an external suggestion. - * Corresponds to Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD. + * Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD. */ @GuardedBy("mLock") private int mDynamicPowerSavingsDisableThreshold; /** * Whether we've received a suggestion that battery saver should be on from an external app. - * Updates when Global.DYNAMIC_POWER_SAVINGS_ENABLED changes. + * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes. */ @GuardedBy("mLock") private boolean mDynamicPowerSavingsBatterySaver; @@ -181,7 +194,7 @@ public class BatterySaverStateMachine { Slog.d(TAG, "onBootCompleted"); } // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it. - putGlobalSetting(Global.LOW_POWER_MODE, 0); + putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0); // This is called with the power manager lock held. Don't do anything that may call to // upper services. (e.g. don't call into AM directly) @@ -199,13 +212,19 @@ public class BatterySaverStateMachine { Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), false, mSettingsObserver, UserHandle.USER_SYSTEM); cr.registerContentObserver(Settings.Global.getUriFor( - Global.AUTOMATIC_POWER_SAVER_MODE), + Settings.Global.AUTOMATIC_POWER_SAVER_MODE), + false, mSettingsObserver, UserHandle.USER_SYSTEM); + cr.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED), + false, mSettingsObserver, UserHandle.USER_SYSTEM); + cr.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD), false, mSettingsObserver, UserHandle.USER_SYSTEM); cr.registerContentObserver(Settings.Global.getUriFor( - Global.DYNAMIC_POWER_SAVINGS_ENABLED), + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED), false, mSettingsObserver, UserHandle.USER_SYSTEM); cr.registerContentObserver(Settings.Global.getUriFor( - Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD), + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL), false, mSettingsObserver, UserHandle.USER_SYSTEM); synchronized (mLock) { @@ -239,25 +258,31 @@ public class BatterySaverStateMachine { } @GuardedBy("mLock") - void refreshSettingsLocked() { + private void refreshSettingsLocked() { final boolean lowPowerModeEnabled = getGlobalSetting( Settings.Global.LOW_POWER_MODE, 0) != 0; final boolean lowPowerModeEnabledSticky = getGlobalSetting( Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0; final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting( - Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0; + Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0; final int lowPowerModeTriggerLevel = getGlobalSetting( Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); - final int automaticBatterySaver = getGlobalSetting( - Global.AUTOMATIC_POWER_SAVER_MODE, + final int automaticBatterySaverMode = getGlobalSetting( + Settings.Global.AUTOMATIC_POWER_SAVER_MODE, PowerManager.POWER_SAVER_MODE_PERCENTAGE); final int dynamicPowerSavingsDisableThreshold = getGlobalSetting( - Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, + Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, mDynamicPowerSavingsDefaultDisableThreshold); + final boolean isStickyAutoDisableEnabled = getGlobalSetting( + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0; + final int stickyAutoDisableThreshold = getGlobalSetting( + Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90); setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky, - lowPowerModeTriggerLevel, automaticBatterySaver, dynamicPowerSavingsBatterySaver, - dynamicPowerSavingsDisableThreshold); + lowPowerModeTriggerLevel, + isStickyAutoDisableEnabled, stickyAutoDisableThreshold, + automaticBatterySaverMode, + dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold); } /** @@ -269,12 +294,16 @@ public class BatterySaverStateMachine { @GuardedBy("mLock") @VisibleForTesting void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, - int batterySaverTriggerThreshold, int automaticBatterySaver, + int batterySaverTriggerThreshold, + boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold, + int automaticBatterySaver, boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) { if (DEBUG) { Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled + " sticky=" + batterySaverEnabledSticky + " threshold=" + batterySaverTriggerThreshold + + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled + + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold + " automaticBatterySaver=" + automaticBatterySaver + " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver + " dynamicPowerSavingsDisableThreshold=" @@ -283,11 +312,19 @@ public class BatterySaverStateMachine { mSettingsLoaded = true; + // Set sensible limits. + stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold, + batterySaverTriggerThreshold); + final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled; final boolean stickyChanged = mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky; final boolean thresholdChanged = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold; + final boolean stickyAutoDisableEnabledChanged = + mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled; + final boolean stickyAutoDisableThresholdChanged = + mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold; final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver; final boolean dynamicPowerSavingsThresholdChanged = mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold; @@ -295,6 +332,7 @@ public class BatterySaverStateMachine { mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver; if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged + || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged || dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) { return; } @@ -302,6 +340,8 @@ public class BatterySaverStateMachine { mSettingBatterySaverEnabled = batterySaverEnabled; mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky; mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold; + mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled; + mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold; mSettingAutomaticBatterySaver = automaticBatterySaver; mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold; mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver; @@ -376,7 +416,9 @@ public class BatterySaverStateMachine { + " mBatterySaverSnoozing=" + mBatterySaverSnoozing + " mIsPowered=" + mIsPowered + " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver - + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky); + + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky + + " mSettingBatterySaverStickyAutoDisableEnabled=" + + mSettingBatterySaverStickyAutoDisableEnabled); } if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { return; // Not fully initialized yet. @@ -392,10 +434,15 @@ public class BatterySaverStateMachine { "Plugged in"); } else if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) { - // Re-enable BS. - enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true, - BatterySaverController.REASON_STICKY_RESTORE, - "Sticky restore"); + if (mSettingBatterySaverStickyAutoDisableEnabled + && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold) { + setStickyActive(false); + } else { + // Re-enable BS. + enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true, + BatterySaverController.REASON_STICKY_RESTORE, + "Sticky restore"); + } } else if (mSettingAutomaticBatterySaver == PowerManager.POWER_SAVER_MODE_PERCENTAGE @@ -483,12 +530,10 @@ public class BatterySaverStateMachine { } mSettingBatterySaverEnabled = enable; - putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0); + putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0); if (manual) { - mSettingBatterySaverEnabledSticky = !mBatterySaverStickyBehaviourDisabled && enable; - putGlobalSetting(Global.LOW_POWER_MODE_STICKY, - mSettingBatterySaverEnabledSticky ? 1 : 0); + setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable); } mBatterySaverController.enableBatterySaver(enable, intReason); @@ -506,7 +551,8 @@ public class BatterySaverStateMachine { } } - private void triggerDynamicModeNotification() { + @VisibleForTesting + void triggerDynamicModeNotification() { NotificationManager manager = mContext.getSystemService(NotificationManager.class); ensureNotificationChannelExists(manager); @@ -553,14 +599,20 @@ public class BatterySaverStateMachine { mBatterySaverSnoozing = snoozing; } + private void setStickyActive(boolean active) { + mSettingBatterySaverEnabledSticky = active; + putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY, + mSettingBatterySaverEnabledSticky ? 1 : 0); + } + @VisibleForTesting protected void putGlobalSetting(String key, int value) { - Global.putInt(mContext.getContentResolver(), key, value); + Settings.Global.putInt(mContext.getContentResolver(), key, value); } @VisibleForTesting protected int getGlobalSetting(String key, int defValue) { - return Global.getInt(mContext.getContentResolver(), key, defValue); + return Settings.Global.getInt(mContext.getContentResolver(), key, defValue); } public void dump(PrintWriter pw) { @@ -597,6 +649,10 @@ public class BatterySaverStateMachine { pw.println(mSettingBatterySaverEnabled); pw.print(" mSettingBatterySaverEnabledSticky="); pw.println(mSettingBatterySaverEnabledSticky); + pw.print(" mSettingBatterySaverStickyAutoDisableEnabled="); + pw.println(mSettingBatterySaverStickyAutoDisableEnabled); + pw.print(" mSettingBatterySaverStickyAutoDisableThreshold="); + pw.println(mSettingBatterySaverStickyAutoDisableThreshold); pw.print(" mSettingBatterySaverTriggerThreshold="); pw.println(mSettingBatterySaverTriggerThreshold); pw.print(" mBatterySaverStickyBehaviourDisabled="); @@ -628,6 +684,13 @@ public class BatterySaverStateMachine { mSettingBatterySaverEnabledSticky); proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD, mSettingBatterySaverTriggerThreshold); + proto.write( + BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED, + mSettingBatterySaverStickyAutoDisableEnabled); + proto.write( + BatterySaverStateMachineProto + .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD, + mSettingBatterySaverStickyAutoDisableThreshold); proto.end(token); } diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index c0517fdc99d0..1c7596b80fd7 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -198,6 +198,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders // for a given role before adding a migration statement for it here migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId); + migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId); // Some vital packages state has changed since last role grant // Run grants again diff --git a/services/core/java/com/android/server/rollback/LocalIntentReceiver.java b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java new file mode 100644 index 000000000000..504a3496147c --- /dev/null +++ b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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.rollback; + +import android.content.IIntentReceiver; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.IBinder; + +import java.util.function.Consumer; + +/** {@code IntentSender} implementation for RollbackManager internal use. */ +class LocalIntentReceiver { + final Consumer<Intent> mConsumer; + + LocalIntentReceiver(Consumer<Intent> consumer) { + mConsumer = consumer; + } + + private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + mConsumer.accept(intent); + } + }; + + public IntentSender getIntentSender() { + return new IntentSender((IIntentSender) mLocalSender); + } +} diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java index f0589aac8239..a4f306489c60 100644 --- a/services/core/java/com/android/server/rollback/RollbackData.java +++ b/services/core/java/com/android/server/rollback/RollbackData.java @@ -29,6 +29,11 @@ import java.util.List; */ class RollbackData { /** + * A unique identifier for this rollback. + */ + public final int rollbackId; + + /** * The per-package rollback information. */ public final List<PackageRollbackInfo> packages = new ArrayList<>(); @@ -44,7 +49,16 @@ class RollbackData { */ public Instant timestamp; - RollbackData(File backupDir) { + /** + * Whether this Rollback is currently in progress. This field is true from the point + * we commit a {@code PackageInstaller} session containing these packages to the point the + * {@code PackageInstaller} calls into the {@code onFinished} callback. + */ + // NOTE: All accesses to this field are from the RollbackManager handler thread. + public boolean inProgress = false; + + RollbackData(int rollbackId, File backupDir) { + this.rollbackId = rollbackId; this.backupDir = backupDir; } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 9df0f72e2240..6487bd7af7ee 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -16,12 +16,9 @@ package com.android.server.rollback; -import android.Manifest; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; @@ -31,37 +28,37 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; -import android.content.pm.StringParceledListSlice; +import android.content.pm.VersionedPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; -import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.storage.StorageManager; import android.util.Log; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageManagerServiceUtils; import java.io.File; import java.io.IOException; +import java.security.SecureRandom; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.Random; /** * Implementation of service that manages APK level rollbacks. @@ -79,6 +76,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // mLock is held when they are called. private final Object mLock = new Object(); + // Used for generating rollback IDs. + private final Random mRandom = new SecureRandom(); + + // Set of allocated rollback ids + @GuardedBy("mLock") + private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray(); + // Package rollback data for rollback-enabled installs that have not yet // been committed. Maps from sessionId to rollback data. @GuardedBy("mLock") @@ -103,14 +107,22 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final Context mContext; private final HandlerThread mHandlerThread; + private final Installer mInstaller; + private final RollbackPackageHealthObserver mPackageHealthObserver; RollbackManagerServiceImpl(Context context) { mContext = context; + // Note that we're calling onStart here because this object is only constructed on + // SystemService#onStart. + mInstaller = new Installer(mContext); + mInstaller.onStart(); mHandlerThread = new HandlerThread("RollbackManagerServiceHandler"); mHandlerThread.start(); mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); + mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); + // Kick off loading of the rollback data from strorage in a background // thread. // TODO: Consider loading the rollback data directly here instead, to @@ -120,8 +132,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // expiration. getHandler().post(() -> ensureRollbackDataLoaded()); - PackageInstaller installer = mContext.getPackageManager().getPackageInstaller(); - installer.registerSessionCallback(new SessionCallback(), getHandler()); + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); + packageInstaller.registerSessionCallback(new SessionCallback(), getHandler()); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); @@ -158,10 +170,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1); int installFlags = intent.getIntExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0); + int[] installedUsers = intent.getIntArrayExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS); File newPackageCodePath = new File(intent.getData().getPath()); getHandler().post(() -> { - boolean success = enableRollback(installFlags, newPackageCodePath); + boolean success = enableRollback(installFlags, newPackageCodePath, + installedUsers); int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED; if (!success) { ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED; @@ -182,48 +197,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } @Override - public RollbackInfo getAvailableRollback(String packageName) { + public ParceledListSlice getAvailableRollbacks() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_ROLLBACKS, - "getAvailableRollback"); - - RollbackData data = getRollbackForPackage(packageName); - if (data == null) { - return null; - } + "getAvailableRollbacks"); - // Note: The rollback for the package ought to be for the currently - // installed version, otherwise the rollback data is out of date. In - // that rare case, we'll check when we execute the rollback whether - // it's out of date or not, so no need to check package versions here. - - for (PackageRollbackInfo info : data.packages) { - if (info.packageName.equals(packageName)) { - // TODO: Once the RollbackInfo API supports info about - // dependant packages, add that info here. - return new RollbackInfo(info); - } - } - return null; - } - - @Override - public StringParceledListSlice getPackagesWithAvailableRollbacks() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_ROLLBACKS, - "getPackagesWithAvailableRollbacks"); - - final Set<String> packageNames = new HashSet<>(); synchronized (mLock) { ensureRollbackDataLoadedLocked(); + List<RollbackInfo> rollbacks = new ArrayList<>(); for (int i = 0; i < mAvailableRollbacks.size(); ++i) { RollbackData data = mAvailableRollbacks.get(i); - for (PackageRollbackInfo info : data.packages) { - packageNames.add(info.packageName); - } + rollbacks.add(new RollbackInfo(data.rollbackId, data.packages)); } + return new ParceledListSlice<>(rollbacks); } - return new StringParceledListSlice(new ArrayList<>(packageNames)); } @Override @@ -261,25 +248,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { */ private void executeRollbackInternal(RollbackInfo rollback, String callerPackageName, IntentSender statusReceiver) { - String targetPackageName = rollback.targetPackage.packageName; - Log.i(TAG, "Initiating rollback of " + targetPackageName); + Log.i(TAG, "Initiating rollback"); - // Get the latest RollbackData for the target package. - RollbackData data = getRollbackForPackage(targetPackageName); + RollbackData data = getRollbackForId(rollback.getRollbackId()); if (data == null) { - sendFailure(statusReceiver, "No rollback available for package."); + sendFailure(statusReceiver, "Rollback unavailable"); return; } - // Verify the latest rollback matches the version requested. - // TODO: Check dependant packages too once RollbackInfo includes that - // information. - for (PackageRollbackInfo info : data.packages) { - if (info.packageName.equals(targetPackageName) - && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) { - sendFailure(statusReceiver, "Rollback is out of date."); - return; - } + if (data.inProgress) { + sendFailure(statusReceiver, "Rollback for package is already in progress."); + return; } // Verify the RollbackData is up to date with what's installed on @@ -291,15 +270,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // Figure out how to ensure we don't commit the rollback if // roll forward happens at the same time. for (PackageRollbackInfo info : data.packages) { - PackageRollbackInfo.PackageVersion installedVersion = - getInstalledPackageVersion(info.packageName); + VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName()); if (installedVersion == null) { // TODO: Test this case sendFailure(statusReceiver, "Package to roll back is not installed"); return; } - if (!info.higherVersion.equals(installedVersion)) { + if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) { // TODO: Test this case sendFailure(statusReceiver, "Package version to roll back not installed."); return; @@ -319,14 +297,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManager pm = context.getPackageManager(); try { PackageInstaller packageInstaller = pm.getPackageInstaller(); - String installerPackageName = pm.getInstallerPackageName(targetPackageName); - if (installerPackageName == null) { - sendFailure(statusReceiver, "Cannot find installer package"); - return; - } PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); - parentParams.setInstallerPackageName(installerPackageName); parentParams.setAllowDowngrade(true); parentParams.setMultiPackage(); int parentSessionId = packageInstaller.createSession(parentParams); @@ -335,6 +307,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); + String installerPackageName = pm.getInstallerPackageName(info.getPackageName()); + if (installerPackageName == null) { + sendFailure(statusReceiver, "Cannot find installer package"); + return; + } params.setInstallerPackageName(installerPackageName); params.setAllowDowngrade(true); int sessionId = packageInstaller.createSession(params); @@ -342,7 +319,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // TODO: Will it always be called "base.apk"? What about splits? // What about apex? - File packageDir = new File(data.backupDir, info.packageName); + File packageDir = new File(data.backupDir, info.getPackageName()); File baseApk = new File(packageDir, "base.apk"); try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk, ParcelFileDescriptor.MODE_READ_ONLY)) { @@ -356,30 +333,41 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { parentSession.addChildSessionId(sessionId); } - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - parentSession.commit(receiver.getIntentSender()); - - Intent result = receiver.getResult(); - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - sendFailure(statusReceiver, "Rollback downgrade install failed: " - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)); - return; - } - - addRecentlyExecutedRollback(rollback); - sendSuccess(statusReceiver); - - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, - Uri.fromParts("package", targetPackageName, - Manifest.permission.MANAGE_ROLLBACKS)); + final LocalIntentReceiver receiver = new LocalIntentReceiver( + (Intent result) -> { + getHandler().post(() -> { + // We've now completed the rollback, so we mark it as no longer in + // progress. + data.inProgress = false; + + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status != PackageInstaller.STATUS_SUCCESS) { + sendFailure(statusReceiver, + "Rollback downgrade install failed: " + + result.getStringExtra( + PackageInstaller.EXTRA_STATUS_MESSAGE)); + return; + } + + addRecentlyExecutedRollback(rollback); + sendSuccess(statusReceiver); + + Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); + + // TODO: This call emits the warning "Calling a method in the + // system process without a qualified user". Fix that. + // TODO: Limit this to receivers holding the + // MANAGE_ROLLBACKS permission? + mContext.sendBroadcast(broadcast); + }); + } + ); - // TODO: This call emits the warning "Calling a method in the - // system process without a qualified user". Fix that. - mContext.sendBroadcast(broadcast); + data.inProgress = true; + parentSession.commit(receiver.getIntentSender()); } catch (IOException e) { - Log.e(TAG, "Unable to roll back " + targetPackageName, e); + Log.e(TAG, "Rollback failed", e); sendFailure(statusReceiver, "IOException: " + e.toString()); return; } @@ -414,7 +402,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { while (iter.hasNext()) { RollbackData data = iter.next(); for (PackageRollbackInfo info : data.packages) { - if (info.packageName.equals(packageName)) { + if (info.getPackageName().equals(packageName)) { iter.remove(); mRollbackStore.deleteAvailableRollback(data); break; @@ -456,7 +444,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @GuardedBy("mLock") private void loadAllRollbackDataLocked() { mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks(); + for (RollbackData data : mAvailableRollbacks) { + mAllocatedRollbackIds.put(data.rollbackId, true); + } + mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks(); + for (RollbackInfo info : mRecentlyExecutedRollbacks) { + mAllocatedRollbackIds.put(info.getRollbackId(), true); + } + scheduleExpiration(0); } @@ -468,8 +464,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private void onPackageReplaced(String packageName) { // TODO: Could this end up incorrectly deleting a rollback for a // package that is about to be installed? - PackageRollbackInfo.PackageVersion installedVersion = - getInstalledPackageVersion(packageName); + VersionedPackage installedVersion = getInstalledPackageVersion(packageName); synchronized (mLock) { ensureRollbackDataLoadedLocked(); @@ -477,8 +472,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { while (iter.hasNext()) { RollbackData data = iter.next(); for (PackageRollbackInfo info : data.packages) { - if (info.packageName.equals(packageName) - && !info.higherVersion.equals(installedVersion)) { + if (info.getPackageName().equals(packageName) + && !packageVersionsEqual( + info.getVersionRolledBackFrom(), + installedVersion)) { iter.remove(); mRollbackStore.deleteAvailableRollback(data); break; @@ -501,9 +498,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { boolean changed = false; while (iter.hasNext()) { RollbackInfo rollback = iter.next(); - if (packageName.equals(rollback.targetPackage.packageName)) { - iter.remove(); - changed = true; + for (PackageRollbackInfo info : rollback.getPackages()) { + if (packageName.equals(info.getPackageName())) { + iter.remove(); + changed = true; + break; + } } } @@ -620,12 +620,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * staged for install with rollback enabled. Called before the package has * been installed. * - * @param id the id of the enable rollback request * @param installFlags information about what is being installed. * @param newPackageCodePath path to the package about to be installed. + * @param installedUsers the set of users for which a given package is installed. * @return true if enabling the rollback succeeds, false otherwise. */ - private boolean enableRollback(int installFlags, File newPackageCodePath) { + private boolean enableRollback(int installFlags, File newPackageCodePath, + int[] installedUsers) { if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Log.e(TAG, "Rollbacks not supported for instant app install"); return false; @@ -675,8 +676,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } - PackageRollbackInfo.PackageVersion newVersion = - new PackageRollbackInfo.PackageVersion(newPackage.versionCode); + VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode); // Get information about the currently installed package. PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); @@ -687,11 +687,29 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Log.e(TAG, packageName + " is not installed"); return false; } - PackageRollbackInfo.PackageVersion installedVersion = - new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode()); + VersionedPackage installedVersion = new VersionedPackage(packageName, + installedPackage.getLongVersionCode()); + + for (int user : installedUsers) { + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(user)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup."); + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } - PackageRollbackInfo info = new PackageRollbackInfo( - packageName, newVersion, installedVersion); + try { + mInstaller.snapshotAppData(packageName, user, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); + } + } + + PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion); RollbackData data; try { @@ -699,7 +717,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mChildSessions.put(childSessionId, parentSessionId); data = mPendingRollbacks.get(parentSessionId); if (data == null) { - data = mRollbackStore.createAvailableRollback(); + int rollbackId = allocateRollbackIdLocked(); + data = mRollbackStore.createAvailableRollback(rollbackId); mPendingRollbacks.put(parentSessionId, data); } data.packages.add(info); @@ -722,40 +741,56 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return true; } - // TODO: Don't copy this from PackageManagerShellCommand like this? - private static class LocalIntentReceiver { - private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); + @Override + public void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("restoureUserData may only be called by the system."); + } - private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { - @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, - IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - try { - mResult.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + getHandler().post(() -> { + PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + final RollbackData rollbackData = getRollbackForPackage(packageName); + if (rollbackData == null) { + pmi.finishPackageInstall(token, false); + return; } - }; - public IntentSender getIntentSender() { - return new IntentSender((IIntentSender) mLocalSender); - } + if (!rollbackData.inProgress) { + Log.e(TAG, "Request to restore userData for: " + packageName + + ", but no rollback in progress."); + pmi.finishPackageInstall(token, false); + return; + } + + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(userId)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore."); + + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } - public Intent getResult() { try { - return mResult.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode, + seInfo, userId, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie); } - } + + pmi.finishPackageInstall(token, false); + }); } /** * Gets the version of the package currently installed. * Returns null if the package is not currently installed. */ - private PackageRollbackInfo.PackageVersion getInstalledPackageVersion(String packageName) { + private VersionedPackage getInstalledPackageVersion(String packageName) { PackageManager pm = mContext.getPackageManager(); PackageInfo pkgInfo = null; try { @@ -764,7 +799,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return null; } - return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode()); + return new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); + } + + private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) { + return a.getPackageName().equals(b.getPackageName()) + && a.getLongVersionCode() == b.getLongVersionCode(); } private class SessionCallback extends PackageInstaller.SessionCallback { @@ -812,7 +852,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { ensureRollbackDataLoadedLocked(); mAvailableRollbacks.add(data); } - + // TODO(zezeozue): Provide API to explicitly start observing instead + // of doing this for all rollbacks. If we do this for all rollbacks, + // should document in PackageInstaller.SessionParams#setEnableRollback + // After enabling and commiting any rollback, observe packages and + // prepare to rollback if packages crashes too frequently. + List<String> packages = new ArrayList<>(); + for (int i = 0; i < data.packages.size(); i++) { + packages.add(data.packages.get(i).getPackageName()); + } + mPackageHealthObserver.startObservingHealth(packages, + ROLLBACK_LIFETIME_DURATION_MILLIS); scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); @@ -840,7 +890,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (int i = 0; i < mAvailableRollbacks.size(); ++i) { RollbackData data = mAvailableRollbacks.get(i); for (PackageRollbackInfo info : data.packages) { - if (info.packageName.equals(packageName)) { + if (info.getPackageName().equals(packageName)) { return data; } } @@ -848,4 +898,38 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } return null; } + + /* + * Returns the RollbackData, if any, for an available rollback with the + * given rollbackId. + */ + private RollbackData getRollbackForId(int rollbackId) { + synchronized (mLock) { + // TODO: Have ensureRollbackDataLoadedLocked return the list of + // available rollbacks, to hopefully avoid forgetting to call it? + ensureRollbackDataLoadedLocked(); + for (int i = 0; i < mAvailableRollbacks.size(); ++i) { + RollbackData data = mAvailableRollbacks.get(i); + if (data.rollbackId == rollbackId) { + return data; + } + } + } + return null; + } + + @GuardedBy("mLock") + private int allocateRollbackIdLocked() throws IOException { + int n = 0; + int rollbackId; + do { + rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; + if (!mAllocatedRollbackIds.get(rollbackId, false)) { + mAllocatedRollbackIds.put(rollbackId, true); + return rollbackId; + } + } while (n++ < 32); + + throw new IOException("Failed to allocate rollback ID"); + } } diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java new file mode 100644 index 000000000000..3954a1178e09 --- /dev/null +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.rollback; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInstaller; +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; +import android.os.Handler; +import android.os.HandlerThread; + +import com.android.server.PackageWatchdog; +import com.android.server.PackageWatchdog.PackageHealthObserver; + +import java.util.List; + +/** + * {@code PackageHealthObserver} for {@code RollbackManagerService}. + * + * @hide + */ +public final class RollbackPackageHealthObserver implements PackageHealthObserver { + private static final String TAG = "RollbackPackageHealthObserver"; + private static final String NAME = "rollback-observer"; + private Context mContext; + private Handler mHandler; + + RollbackPackageHealthObserver(Context context) { + mContext = context; + HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); + handlerThread.start(); + mHandler = handlerThread.getThreadHandler(); + PackageWatchdog.getInstance(mContext).registerHealthObserver(this); + } + + @Override + public boolean onHealthCheckFailed(String packageName) { + RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); + for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) { + for (PackageRollbackInfo packageRollback : rollback.getPackages()) { + if (packageName.equals(packageRollback.getPackageName())) { + // TODO(zezeozue): Only rollback if rollback version == failed package version + mHandler.post(() -> executeRollback(rollbackManager, rollback)); + return true; + } + } + } + // Don't handle the notification, no rollbacks available + return false; + } + + /** + * Start observing health of {@code packages} for {@code durationMs}. + * This may cause {@code packages} to be rolled back if they crash too freqeuntly. + */ + public void startObservingHealth(List<String> packages, long durationMs) { + PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs); + } + + private void executeRollback(RollbackManager manager, RollbackInfo rollback) { + // TODO(zezeozue): Log initiated metrics + LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> { + mHandler.post(() -> { + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + // TODO(zezeozue); Log success metrics + // Rolledback successfully, no action required by other observers + } else { + // TODO(zezeozue); Log failure metrics + // Rollback failed other observers should have a shot + } + }); + }); + manager.commitRollback(rollback, rollbackReceiver.getIntentSender()); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index f9a838fbf267..3b24b3e9a444 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -16,6 +16,7 @@ package com.android.server.rollback; +import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.util.Log; @@ -29,7 +30,6 @@ import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.ArrayList; @@ -58,7 +58,7 @@ class RollbackStore { // base.apk // recently_executed.json // - // * XXX, YYY are random strings from Files.createTempDirectory + // * XXX, YYY are the rollbackIds for the corresponding rollbacks. // * rollback.json contains all relevant metadata for the rollback. This // file is not written until the rollback is made available. // @@ -113,13 +113,10 @@ class RollbackStore { JSONArray array = object.getJSONArray("recentlyExecuted"); for (int i = 0; i < array.length(); ++i) { JSONObject element = array.getJSONObject(i); - String packageName = element.getString("packageName"); - long higherVersionCode = element.getLong("higherVersionCode"); - long lowerVersionCode = element.getLong("lowerVersionCode"); - PackageRollbackInfo target = new PackageRollbackInfo(packageName, - new PackageRollbackInfo.PackageVersion(higherVersionCode), - new PackageRollbackInfo.PackageVersion(lowerVersionCode)); - RollbackInfo rollback = new RollbackInfo(target); + int rollbackId = element.getInt("rollbackId"); + List<PackageRollbackInfo> packages = packageRollbackInfosFromJson( + element.getJSONArray("packages")); + RollbackInfo rollback = new RollbackInfo(rollbackId, packages); recentlyExecutedRollbacks.add(rollback); } } catch (IOException | JSONException e) { @@ -135,9 +132,9 @@ class RollbackStore { /** * Creates a new RollbackData instance with backupDir assigned. */ - RollbackData createAvailableRollback() throws IOException { - File backupDir = Files.createTempDirectory(mAvailableRollbacksDir.toPath(), null).toFile(); - return new RollbackData(backupDir); + RollbackData createAvailableRollback(int rollbackId) throws IOException { + File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId)); + return new RollbackData(rollbackId, backupDir); } /** @@ -154,15 +151,8 @@ class RollbackStore { void saveAvailableRollback(RollbackData data) throws IOException { try { JSONObject dataJson = new JSONObject(); - JSONArray packagesJson = new JSONArray(); - for (PackageRollbackInfo info : data.packages) { - JSONObject infoJson = new JSONObject(); - infoJson.put("packageName", info.packageName); - infoJson.put("higherVersionCode", info.higherVersion.versionCode); - infoJson.put("lowerVersionCode", info.lowerVersion.versionCode); - packagesJson.put(infoJson); - } - dataJson.put("packages", packagesJson); + dataJson.put("rollbackId", data.rollbackId); + dataJson.put("packages", toJson(data.packages)); dataJson.put("timestamp", data.timestamp.toString()); PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json")); @@ -178,6 +168,8 @@ class RollbackStore { * rollback. */ void deleteAvailableRollback(RollbackData data) { + // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the + // actual app. removeFile(data.backupDir); } @@ -193,9 +185,8 @@ class RollbackStore { for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) { RollbackInfo rollback = recentlyExecutedRollbacks.get(i); JSONObject element = new JSONObject(); - element.put("packageName", rollback.targetPackage.packageName); - element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode); - element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode); + element.put("rollbackId", rollback.getRollbackId()); + element.put("packages", toJson(rollback.getPackages())); array.put(element); } @@ -214,21 +205,13 @@ class RollbackStore { */ private RollbackData loadRollbackData(File backupDir) throws IOException { try { - RollbackData data = new RollbackData(backupDir); File rollbackJsonFile = new File(backupDir, "rollback.json"); JSONObject dataJson = new JSONObject( IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath())); - JSONArray packagesJson = dataJson.getJSONArray("packages"); - for (int i = 0; i < packagesJson.length(); ++i) { - JSONObject infoJson = packagesJson.getJSONObject(i); - String packageName = infoJson.getString("packageName"); - long higherVersionCode = infoJson.getLong("higherVersionCode"); - long lowerVersionCode = infoJson.getLong("lowerVersionCode"); - data.packages.add(new PackageRollbackInfo(packageName, - new PackageRollbackInfo.PackageVersion(higherVersionCode), - new PackageRollbackInfo.PackageVersion(lowerVersionCode))); - } + int rollbackId = dataJson.getInt("rollbackId"); + RollbackData data = new RollbackData(rollbackId, backupDir); + data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages"))); data.timestamp = Instant.parse(dataJson.getString("timestamp")); return data; } catch (JSONException | DateTimeParseException e) { @@ -236,6 +219,40 @@ class RollbackStore { } } + private JSONObject toJson(PackageRollbackInfo info) throws JSONException { + JSONObject json = new JSONObject(); + json.put("packageName", info.getPackageName()); + json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode()); + json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode()); + return json; + } + + private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException { + String packageName = json.getString("packageName"); + long higherVersionCode = json.getLong("higherVersionCode"); + long lowerVersionCode = json.getLong("lowerVersionCode"); + return new PackageRollbackInfo( + new VersionedPackage(packageName, higherVersionCode), + new VersionedPackage(packageName, lowerVersionCode)); + } + + private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException { + JSONArray json = new JSONArray(); + for (PackageRollbackInfo info : infos) { + json.put(toJson(info)); + } + return json; + } + + private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json) + throws JSONException { + List<PackageRollbackInfo> infos = new ArrayList<>(); + for (int i = 0; i < json.length(); ++i) { + infos.add(packageRollbackInfoFromJson(json.getJSONObject(i))); + } + return infos; + } + /** * Deletes a file completely. * If the file is a directory, its contents are deleted as well. diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java index 56db32a3071d..146c51688531 100644 --- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java +++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java @@ -41,8 +41,8 @@ public class SignatureVerifier { private static final boolean DBG = false; private static final String DEBUG_KEY = - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60" - + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ=="; + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1" + + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg=="; private static final String PROD_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp" + "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g=="; diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 40664fef2825..c6d2870a24c9 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -246,6 +246,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader; + private long mDebugElapsedClockPreviousValue = 0; + private long mDebugElapsedClockPullCount = 0; + private long mDebugFailingElapsedClockPreviousValue = 0; + private long mDebugFailingElapsedClockPullCount = 0; private BatteryStatsHelper mBatteryStatsHelper = null; private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; @@ -1705,6 +1709,77 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + long callingToken = Binder.clearCallingIdentity(); + try { + List<Temperature> temperatures = sThermalService.getCurrentTemperatures(); + for (Temperature temp : temperatures) { + StatsLogEventWrapper e = + new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(temp.getType()); + e.writeString(temp.getName()); + e.writeInt((int) (temp.getValue() * 10)); + pulledData.add(e); + } + } catch (RemoteException e) { + // Should not happen. + Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures."); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + } + + private void pullDebugElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + final long elapsedMillis = SystemClock.elapsedRealtime(); + final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue; + + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeLong(mDebugElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(clockDiffMillis); + e.writeInt(1 /* always set */); + pulledData.add(e); + + if (mDebugElapsedClockPullCount % 2 == 1) { + StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e2.writeLong(mDebugElapsedClockPullCount); + e2.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e2.writeLong(elapsedMillis); + e2.writeLong(clockDiffMillis); + e2.writeInt(2 /* set on odd pulls */); + pulledData.add(e2); + } + + mDebugElapsedClockPullCount++; + mDebugElapsedClockPreviousValue = elapsedMillis; + } + + private void pullDebugFailingElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + final long elapsedMillis = SystemClock.elapsedRealtime(); + // Fails every 10 buckets. + if (mDebugFailingElapsedClockPullCount++ % 10 == 0) { + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + throw new RuntimeException("Failing debug elapsed clock"); + } + + e.writeLong(mDebugFailingElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(mDebugFailingElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue); + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + pulledData.add(e); + } + /** * Pulls various data. */ @@ -1822,7 +1897,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret); break; } - case StatsLog.NUM_FINGERPRINTS: { + case StatsLog.NUM_FINGERPRINTS_ENROLLED: { pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret); break; } @@ -1867,6 +1942,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.TEMPERATURE: { + pullTemperature(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.DEBUG_ELAPSED_CLOCK: { + pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: { + pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java new file mode 100644 index 000000000000..4adce586e9a5 --- /dev/null +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.testharness; + +import android.annotation.Nullable; +import android.app.KeyguardManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.BatteryManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.SystemService; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +/** + * Manages the Test Harness Mode service for setting up test harness mode on the device. + * + * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys, + * and provision the device for Instrumentation testing. This means that all parts of the device + * that would otherwise interfere with testing (auto-syncing accounts, package verification, + * automatic updates, etc.) are all disabled by default but may be re-enabled by the user. + */ +public class TestHarnessModeService extends SystemService { + private static final String TAG = TestHarnessModeService.class.getSimpleName(); + private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; + + private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; + private boolean mShouldSetUpTestHarnessMode; + + public TestHarnessModeService(Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService("testharness", mService); + } + + @Override + public void onBootPhase(int phase) { + switch (phase) { + case PHASE_SYSTEM_SERVICES_READY: + setUpTestHarnessMode(); + break; + case PHASE_BOOT_COMPLETED: + disableAutoSync(); + break; + } + super.onBootPhase(phase); + } + + private void setUpTestHarnessMode() { + Slog.d(TAG, "Setting up test harness mode"); + byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData(); + if (testHarnessModeData == null || testHarnessModeData.length == 0) { + // There's no data to apply, so leave it as-is. + return; + } + mShouldSetUpTestHarnessMode = true; + PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData); + + SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0"); + writeAdbKeysFile(persistentData); + // Clear out the data block so that we don't revert the ADB keys on every boot. + getPersistentDataBlock().clearTestHarnessModeData(); + + ContentResolver cr = getContext().getContentResolver(); + if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { + // Enable ADB + Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1); + } else { + // ADB is already enabled, we should restart the service so it picks up the new keys + android.os.SystemService.restart("adbd"); + } + + Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); + Settings.Global.putInt( + cr, + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, + BatteryManager.BATTERY_PLUGGED_ANY); + Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1); + Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + setDeviceProvisioned(); + } + + private void disableAutoSync() { + if (!mShouldSetUpTestHarnessMode) { + return; + } + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + ContentResolver + .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier()); + } + + private void writeAdbKeysFile(PersistentData persistentData) { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + try { + OutputStream fileOutputStream = Files.newOutputStream(adbKeys); + fileOutputStream.write(persistentData.mAdbKeys); + fileOutputStream.close(); + + Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys); + permissions.add(PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(adbKeys, permissions); + } catch (IOException e) { + Slog.e(TAG, "Failed to set up adb keys", e); + // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all + // other settings will be set up. + } + } + + // Setting the device as provisioned skips the setup wizard. + private void setDeviceProvisioned() { + ContentResolver cr = getContext().getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putIntForUser( + cr, + Settings.Secure.USER_SETUP_COMPLETE, + 1, + UserHandle.USER_CURRENT); + } + + @Nullable + private PersistentDataBlockManagerInternal getPersistentDataBlock() { + if (mPersistentDataBlockManagerInternal == null) { + Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices"); + mPersistentDataBlockManagerInternal = + LocalServices.getService(PersistentDataBlockManagerInternal.class); + } + return mPersistentDataBlockManagerInternal; + } + + private final IBinder mService = new Binder() { + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + (new TestHarnessModeShellCommand()) + .exec(this, in, out, err, args, callback, resultReceiver); + } + }; + + private class TestHarnessModeShellCommand extends ShellCommand { + @Override + public int onCommand(String cmd) { + switch (cmd) { + case "enable": + case "restore": + checkPermissions(); + final long originalId = Binder.clearCallingIdentity(); + try { + if (isDeviceSecure()) { + getErrPrintWriter().println( + "Test Harness Mode cannot be enabled if there is a lock " + + "screen"); + return 2; + } + return handleEnable(); + } finally { + Binder.restoreCallingIdentity(originalId); + } + default: + return handleDefaultCommands(cmd); + } + } + + private void checkPermissions() { + getContext().enforceCallingPermission( + android.Manifest.permission.ENABLE_TEST_HARNESS_MODE, + "You must hold android.permission.ENABLE_TEST_HARNESS_MODE " + + "to enable Test Harness Mode"); + } + + private boolean isDeviceSecure() { + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class); + return keyguardManager.isDeviceSecure(primaryUser.id); + } + + private int handleEnable() { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + if (!Files.exists(adbKeys)) { + // This should only be accessible on eng builds that haven't yet set up ADB keys + getErrPrintWriter() + .println("No ADB keys stored; not enabling test harness mode"); + return 1; + } + + try (InputStream inputStream = Files.newInputStream(adbKeys)) { + long size = Files.size(adbKeys); + byte[] adbKeysBytes = new byte[(int) size]; + int numBytes = inputStream.read(adbKeysBytes); + if (numBytes != size) { + getErrPrintWriter().println("Failed to read all bytes of adb_keys"); + return 1; + } + PersistentData persistentData = new PersistentData(true, adbKeysBytes); + getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes()); + } catch (IOException e) { + Slog.e(TAG, "Failed to store ADB keys.", e); + getErrPrintWriter().println("Failed to enable Test Harness Mode"); + return 1; + } + + Intent i = new Intent(Intent.ACTION_FACTORY_RESET); + i.setPackage("android"); + i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + i.putExtra(Intent.EXTRA_REASON, TAG); + i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true); + getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM); + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("About:"); + pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare"); + pw.println(" the device for running UI tests. The device is placed into this mode by"); + pw.println(" first wiping all data from the device, preserving ADB keys."); + pw.println(); + pw.println(" By default, the following settings are configured:"); + pw.println(" * Package Verifier is disabled"); + pw.println(" * Stay Awake While Charging is enabled"); + pw.println(" * OTA Updates are disabled"); + pw.println(" * Auto-Sync for accounts is disabled"); + pw.println(); + pw.println(" Other apps may configure themselves differently in Test Harness Mode by"); + pw.println(" checking ActivityManager.isRunningInUserTestHarness()"); + pw.println(); + pw.println("Test Harness Mode commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" enable|restore"); + pw.println(" Erase all data from this device and enable Test Harness Mode,"); + pw.println(" preserving the stored ADB keys currently on the device and toggling"); + pw.println(" settings in a way that are conducive to Instrumentation testing."); + } + } + + /** + * The object that will serialize/deserialize the Test Harness Mode data to and from the + * persistent data block. + */ + public static class PersistentData { + static final byte VERSION_1 = 1; + + final int mVersion; + final boolean mEnabled; + final byte[] mAdbKeys; + + PersistentData(boolean enabled, byte[] adbKeys) { + this(VERSION_1, enabled, adbKeys); + } + + PersistentData(int version, boolean enabled, byte[] adbKeys) { + this.mVersion = version; + this.mEnabled = enabled; + this.mAdbKeys = adbKeys; + } + + static PersistentData fromBytes(byte[] bytes) { + try { + DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); + int version = is.readInt(); + boolean enabled = is.readBoolean(); + int adbKeysLength = is.readInt(); + byte[] adbKeys = new byte[adbKeysLength]; + is.readFully(adbKeys); + return new PersistentData(version, enabled, adbKeys); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + byte[] toBytes() { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(VERSION_1); + dos.writeBoolean(mEnabled); + dos.writeInt(mAdbKeys.length); + dos.write(mAdbKeys); + dos.close(); + return os.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 9a7e75e548af..744efab7d78d 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -1129,7 +1129,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * In this case, we grant a uri permission, even if the ContentProvider does not normally * grant uri permissions. */ - boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId + boolean specialCrossUserGrant = targetUid >= 0 + && UserHandle.getUserId(targetUid) != grantUri.sourceUserId && checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid, modeFlags, false /*without considering the uid permissions*/); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3aef8e1f84bf..c97e4e8a9bc0 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2295,7 +2295,7 @@ class ActivityStack extends ConfigurationContainer { * Check if the display to which this stack is attached has * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied. */ - private boolean canShowWithInsecureKeyguard() { + boolean canShowWithInsecureKeyguard() { final ActivityDisplay activityDisplay = getDisplay(); if (activityDisplay == null) { throw new IllegalStateException("Stack is not attached to any display, stackId=" diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 4e2dffc2ba78..22a81efc033a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -747,12 +747,23 @@ class ActivityStarter { abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); - // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking - // on START_ABORTED + boolean abortBackgroundStart = false; if (!abort) { - abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, - realCallingUid, callerApp, originatingPendingIntent, + abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid, + callingPackage, realCallingUid, callerApp, originatingPendingIntent, allowBackgroundActivityStart, intent); + abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled()); + // TODO: remove this toast after feature development is done + if (abortBackgroundStart) { + final String toastMsg = abort + ? "Background activity start from " + callingPackage + + " blocked. See go/q-bg-block." + : "This background activity start from " + callingPackage + + " will be blocked in future Q builds. See go/q-bg-block."; + mService.mUiHandler.post(() -> { + Toast.makeText(mService.mContext, toastMsg, Toast.LENGTH_LONG).show(); + }); + } } // Merge the two options bundles, while realCallerOptions takes precedence. @@ -798,8 +809,6 @@ class ActivityStarter { // We pretend to the caller that it was really started, but // they will just get a cancel result. ActivityOptions.abort(checkedOptions); - maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, - null /*r*/, originatingPendingIntent, true /*abortedStart*/); return START_ABORTED; } @@ -818,6 +827,8 @@ class ActivityStarter { final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags + | FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -892,8 +903,11 @@ class ActivityStarter { mService.onStartActivitySetDidAppSwitch(); mController.doPendingActivityLaunches(false); - maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r, - originatingPendingIntent, false /*abortedStart*/); + // maybe log to TRON, but only if we haven't already in shouldAbortBackgroundActivityStart() + if (!abortBackgroundStart) { + maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r, + originatingPendingIntent, false /*abortedStart*/); + } return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, outActivity); @@ -903,11 +917,9 @@ class ActivityStarter { final String callingPackage, int realCallingUid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart, Intent intent) { - if (mService.isBackgroundActivityStartsEnabled()) { - return false; - } // don't abort for the most important UIDs - if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { + if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID + || callingUid == Process.NFC_UID) { return false; } // don't abort if the callerApp has any visible activity @@ -915,14 +927,15 @@ class ActivityStarter { return false; } // don't abort if the callingUid is in the foreground or is a persistent system process - final boolean isCallingUidForeground = isUidForeground(callingUid); + final boolean isCallingUidForeground = mService.isUidForeground(callingUid); final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( callingUid); if (isCallingUidForeground || isCallingUidPersistentSystemProcess) { return false; } // take realCallingUid into consideration - final boolean isRealCallingUidForeground = isUidForeground(realCallingUid); + final boolean isRealCallingUidForeground = mService.isUidForeground( + realCallingUid); final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( realCallingUid); if (realCallingUid != callingUid) { @@ -949,8 +962,8 @@ class ActivityStarter { if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { return false; } - // anything that has fallen through will currently be aborted - Slog.w(TAG, "Blocking background activity start [callingPackage: " + callingPackage + // anything that has fallen through would currently be aborted + Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage + "; callingUid: " + callingUid + "; isCallingUidForeground: " + isCallingUidForeground + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess @@ -962,21 +975,11 @@ class ActivityStarter { + "; isBgStartWhitelisted: " + allowBackgroundActivityStart + "; intent: " + intent + "]"); - // TODO: remove this toast after feature development is done - mService.mUiHandler.post(() -> { - Toast.makeText(mService.mContext, - "Blocking background activity start for " + callingPackage, - Toast.LENGTH_SHORT).show(); - }); + maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, + null /*r*/, originatingPendingIntent, true /*abortedStart*/); return true; } - /** Returns true if uid has a visible window or its process is in a top state. */ - private boolean isUidForeground(int uid) { - return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP) - || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid); - } - /** Returns true if uid is in a persistent state. */ private boolean isUidPersistentSystemProcess(int uid) { return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 67b00b2cfbf1..1a5e6a14e733 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -489,4 +489,7 @@ public abstract class ActivityTaskManagerInternal { */ public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution); + + /** Returns true if uid has a visible window or its process is in a top state. */ + public abstract boolean isUidForeground(int uid); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 61c4863c0bc9..5fabde45db55 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4399,6 +4399,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void registerRemoteAnimationsForDisplay(int displayId, + RemoteAnimationDefinition definition) { + mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "registerRemoteAnimations"); + definition.setCallingPid(Binder.getCallingPid()); + synchronized (mGlobalLock) { + final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId); + if (display == null) { + Slog.e(TAG, "Couldn't find display with id: " + displayId); + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + display.mDisplayContent.registerRemoteAnimations(definition); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ @Override public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) { @@ -5313,9 +5334,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } void updateActivityUsageStats(ActivityRecord activity, int event) { + ComponentName taskRoot = null; + final TaskRecord task = activity.getTaskRecord(); + if (task != null) { + final ActivityRecord rootActivity = task.getRootActivity(); + if (rootActivity != null) { + taskRoot = rootActivity.mActivityComponent; + } + } + final Message m = PooledLambda.obtainMessage( ActivityManagerInternal::updateActivityUsageStats, mAmInternal, - activity.mActivityComponent, activity.mUserId, event, activity.appToken); + activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot); mH.sendMessage(m); } @@ -5623,6 +5653,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mActiveUids.get(uid, PROCESS_STATE_NONEXISTENT); } + boolean isUidForeground(int uid) { + return (getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP) + || mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid); + } + /** * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on * the whitelist @@ -6213,30 +6248,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } return; } - mH.post(() -> { - synchronized (mGlobalLock) { - final ActivityDisplay activityDisplay = - mRootActivityContainer.getActivityDisplay(displayId); - if (activityDisplay == null) { - // Call might come when display is not yet added or has been removed. - if (DEBUG_CONFIGURATION) { - Slog.w(TAG, "Trying to update display configuration for non-existing " - + "displayId=" + displayId); - } - return; + synchronized (mGlobalLock) { + final ActivityDisplay activityDisplay = + mRootActivityContainer.getActivityDisplay(displayId); + if (activityDisplay == null) { + // Call might come when display is not yet added or has been removed. + if (DEBUG_CONFIGURATION) { + Slog.w(TAG, "Trying to update display configuration for non-existing " + + "displayId=" + displayId); } - final WindowProcessController process = mPidMap.get(pid); - if (process == null) { - if (DEBUG_CONFIGURATION) { - Slog.w(TAG, "Trying to update display configuration for invalid " - + "process, pid=" + pid); - } - return; + return; + } + final WindowProcessController process = mPidMap.get(pid); + if (process == null) { + if (DEBUG_CONFIGURATION) { + Slog.w(TAG, "Trying to update display configuration for invalid " + + "process, pid=" + pid); } - process.registerDisplayConfigurationListenerLocked(activityDisplay); + return; } - }); - + process.registerDisplayConfigurationListenerLocked(activityDisplay); + } } @Override @@ -7035,5 +7067,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution); } } + + @Override + public boolean isUidForeground(int uid) { + synchronized (mGlobalLock) { + return ActivityTaskManagerService.this.isUidForeground(uid); + } + } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 5f393ef59057..6dc73bbb80cb 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPE import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; import static android.view.WindowManager.TRANSIT_TASK_OPEN; @@ -174,6 +175,7 @@ public class AppTransition implements Dump { private int mLastUsedAppTransition = TRANSIT_UNSET; private String mLastOpeningApp; private String mLastClosingApp; + private String mLastChangingApp; private static final int NEXT_TRANSIT_TYPE_NONE = 0; private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1; @@ -318,14 +320,16 @@ public class AppTransition implements Dump { private void setAppTransition(int transit, int flags) { mNextAppTransition = transit; mNextAppTransitionFlags |= flags; - setLastAppTransition(TRANSIT_UNSET, null, null); + setLastAppTransition(TRANSIT_UNSET, null, null, null); updateBooster(); } - void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) { + void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp, + AppWindowToken changingApp) { mLastUsedAppTransition = transit; mLastOpeningApp = "" + openingApp; mLastClosingApp = "" + closingApp; + mLastChangingApp = "" + changingApp; } boolean isReady() { @@ -412,9 +416,7 @@ public class AppTransition implements Dump { * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another * layout pass needs to be done */ - int goodToGo(int transit, AppWindowToken topOpeningApp, - AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps, - ArraySet<AppWindowToken> closingApps) { + int goodToGo(int transit, AppWindowToken topOpeningApp, ArraySet<AppWindowToken> openingApps) { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); @@ -422,8 +424,6 @@ public class AppTransition implements Dump { ? topOpeningApp.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked(transit, - topOpeningApp != null ? topOpeningApp.token : null, - topClosingApp != null ? topClosingApp.token : null, topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, topOpeningAnim != null ? topOpeningAnim.getStatusBarTransitionsStartTime() @@ -500,13 +500,12 @@ public class AppTransition implements Dump { } } - private int notifyAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + private int notifyAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { - redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken, - closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration); + redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration, + statusBarAnimationStartTime, statusBarAnimationDuration); } return redoLayout; } @@ -1664,6 +1663,15 @@ public class AppTransition implements Dump { "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:" + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=true" + " Callers=" + Debug.getCallers(3)); + } else if (transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE) { + // In the absence of a specific adapter, we just want to keep everything stationary. + a = new AlphaAnimation(1.f, 1.f); + a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { + Slog.v(TAG, "applyAnimation:" + + " anim=" + a + " transit=" + appTransitionToString(transit) + + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); + } } else { int animAttr = 0; switch (transit) { @@ -2128,6 +2136,8 @@ public class AppTransition implements Dump { pw.println(mLastOpeningApp); pw.print(prefix); pw.print("mLastClosingApp="); pw.println(mLastClosingApp); + pw.print(prefix); pw.print("mLastChangingApp="); + pw.println(mLastChangingApp); } } @@ -2226,14 +2236,16 @@ public class AppTransition implements Dump { if (dc == null) { return; } - if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) { + if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty() + || !dc.mChangingApps.isEmpty()) { if (DEBUG_APP_TRANSITIONS) { Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT." + " displayId=" + dc.getDisplayId() + " isTransitionSet()=" + dc.mAppTransition.isTransitionSet() + " mOpeningApps.size()=" + dc.mOpeningApps.size() - + " mClosingApps.size()=" + dc.mClosingApps.size()); + + " mClosingApps.size()=" + dc.mClosingApps.size() + + " mChangingApps.size()=" + dc.mChangingApps.size()); } setTimeout(); mService.mWindowPlacerLocked.performSurfacePlacement(); diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index bf00ffb30222..8f0a7c08fca8 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -49,8 +49,8 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIO import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING; +import android.os.SystemClock; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; @@ -76,6 +76,7 @@ public class AppTransitionController { private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final WallpaperController mWallpaperControllerLocked; + private RemoteAnimationDefinition mRemoteAnimationDefinition = null; private final SparseIntArray mTempTransitionReasons = new SparseIntArray(); @@ -85,12 +86,17 @@ public class AppTransitionController { mWallpaperControllerLocked = mDisplayContent.mWallpaperController; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mRemoteAnimationDefinition = definition; + } + /** * Handle application transition for given display. */ void handleAppTransitionReady() { - final int appsCount = mDisplayContent.mOpeningApps.size(); - if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) { + mTempTransitionReasons.clear(); + if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) + || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); @@ -108,21 +114,25 @@ public class AppTransitionController { mDisplayContent.mWallpaperMayChange = false; - int i; - for (i = 0; i < appsCount; i++) { - final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i); + int appCount = mDisplayContent.mOpeningApps.size(); + for (int i = 0; i < appCount; ++i) { // Clearing the mAnimatingExit flag before entering animation. It's set to true if app // window is removed, or window relayout to invisible. This also affects window // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the // transition selection depends on wallpaper target visibility. - wtoken.clearAnimatingFlags(); + mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags(); + } + appCount = mDisplayContent.mChangingApps.size(); + for (int i = 0; i < appCount; ++i) { + // Clearing for same reason as above. + mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags(); } // Adjust wallpaper before we pull the lower/upper target, since pending changes // (like the clearAnimatingFlags() above) might affect wallpaper target result. // Or, the opening app window should be a wallpaper target. mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded( - mDisplayContent.mOpeningApps); + mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps); // Determine if closing and opening app token sets are wallpaper targets, in which case // special animations are needed. @@ -141,7 +151,7 @@ public class AppTransitionController { // no need to do an animation. This is the case, for example, when this transition is being // done behind a dream window. final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps); + mDisplayContent.mClosingApps, mDisplayContent.mChangingApps); final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw(); final AppWindowToken animLpToken = allowAnimations ? findAnimLayoutParamsToken(transit, activityTypes) @@ -152,11 +162,15 @@ public class AppTransitionController { final AppWindowToken topClosingApp = allowAnimations ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */) : null; + final AppWindowToken topChangingApp = allowAnimations + ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */) + : null; final WindowManager.LayoutParams animLp = getAnimLp(animLpToken); overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes); final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps) - || containsVoiceInteraction(mDisplayContent.mOpeningApps); + || containsVoiceInteraction(mDisplayContent.mOpeningApps) + || containsVoiceInteraction(mDisplayContent.mChangingApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); @@ -165,13 +179,14 @@ public class AppTransitionController { handleClosingApps(transit, animLp, voiceInteraction); handleOpeningApps(transit, animLp, voiceInteraction); + handleChangingApps(transit, animLp, voiceInteraction); appTransition.setLastAppTransition(transit, topOpeningApp, - topClosingApp); + topClosingApp, topChangingApp); final int flags = appTransition.getTransitFlags(); layoutRedo = appTransition.goodToGo(transit, topOpeningApp, - topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps); + mDisplayContent.mOpeningApps); handleNonAppWindowsInTransition(transit, flags); appTransition.postAnimationCallback(); appTransition.clear(); @@ -183,6 +198,7 @@ public class AppTransitionController { mDisplayContent.mOpeningApps.clear(); mDisplayContent.mClosingApps.clear(); + mDisplayContent.mChangingApps.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); // This has changed the visibility of windows, so perform @@ -191,8 +207,8 @@ public class AppTransitionController { mDisplayContent.computeImeTarget(true /* updateImeTarget */); - mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, - mTempTransitionReasons.clone()).sendToTarget(); + mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(), + SystemClock.uptimeMillis()); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); @@ -205,6 +221,21 @@ public class AppTransitionController { return mainWindow != null ? mainWindow.mAttrs : null; } + RemoteAnimationAdapter getRemoteAnimationOverride(AppWindowToken animLpToken, int transit, + ArraySet<Integer> activityTypes) { + final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition(); + if (definition != null) { + final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes); + if (adapter != null) { + return adapter; + } + } + if (mRemoteAnimationDefinition == null) { + return null; + } + return mRemoteAnimationDefinition.getAdapter(transit, activityTypes); + } + /** * Overrides the pending transition with the remote animation defined for the transition in the * set of defined remote animations in the app window token. @@ -218,11 +249,8 @@ public class AppTransitionController { if (animLpToken == null) { return; } - final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition(); - if (definition == null) { - return; - } - final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes); + final RemoteAnimationAdapter adapter = + getRemoteAnimationOverride(animLpToken, transit, activityTypes); if (adapter != null) { animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote( adapter); @@ -237,29 +265,30 @@ public class AppTransitionController { AppWindowToken result; final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps; final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps; + final ArraySet<AppWindowToken> changingApps = mDisplayContent.mChangingApps; // Remote animations always win, but fullscreen tokens override non-fullscreen tokens. - result = lookForHighestTokenWithFilter(closingApps, openingApps, + result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.getRemoteAnimationDefinition() != null && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); if (result != null) { return result; } - result = lookForHighestTokenWithFilter(closingApps, openingApps, + result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.fillsParent() && w.findMainWindow() != null); if (result != null) { return result; } - return lookForHighestTokenWithFilter(closingApps, openingApps, + return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.findMainWindow() != null); } /** * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set - * of apps in {@code array1} and {@code array2}. + * of apps in {@code array1}, {@code array2}, and {@code array3}. */ private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1, - ArraySet<AppWindowToken> array2) { + ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3) { final ArraySet<Integer> result = new ArraySet<>(); for (int i = array1.size() - 1; i >= 0; i--) { result.add(array1.valueAt(i).getActivityType()); @@ -267,19 +296,26 @@ public class AppTransitionController { for (int i = array2.size() - 1; i >= 0; i--) { result.add(array2.valueAt(i).getActivityType()); } + for (int i = array3.size() - 1; i >= 0; i--) { + result.add(array3.valueAt(i).getActivityType()); + } return result; } private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1, - ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) { - final int array1count = array1.size(); - final int count = array1count + array2.size(); + ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3, + Predicate<AppWindowToken> filter) { + final int array2base = array1.size(); + final int array3base = array2.size() + array2base; + final int count = array3base + array3.size(); int bestPrefixOrderIndex = Integer.MIN_VALUE; AppWindowToken bestToken = null; for (int i = 0; i < count; i++) { - final AppWindowToken wtoken = i < array1count + final AppWindowToken wtoken = i < array2base ? array1.valueAt(i) - : array2.valueAt(i - array1count); + : (i < array3base + ? array2.valueAt(i - array2base) + : array3.valueAt(i - array3base)); final int prefixOrderIndex = wtoken.getPrefixOrderIndex(); if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) { bestPrefixOrderIndex = prefixOrderIndex; @@ -360,6 +396,24 @@ public class AppTransitionController { } } + private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) { + final ArraySet<AppWindowToken> apps = mDisplayContent.mChangingApps; + final int appsCount = apps.size(); + for (int i = 0; i < appsCount; i++) { + AppWindowToken wtoken = apps.valueAt(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now changing app" + wtoken); + wtoken.cancelAnimationOnly(); + wtoken.applyAnimationLocked(null, transit, true, false); + wtoken.updateReportedVisibilityLocked(); + mService.openSurfaceTransaction(); + try { + wtoken.showAllWindowsLocked(); + } finally { + mService.closeSurfaceTransaction("handleChangingApps"); + } + } + } + private void handleNonAppWindowsInTransition(int transit, int flags) { if (transit == TRANSIT_KEYGUARD_GOING_AWAY) { if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 @@ -379,16 +433,15 @@ public class AppTransitionController { } } - private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) { + private boolean transitionGoodToGo(ArraySet<AppWindowToken> apps, SparseIntArray outReasons) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Checking " + appsCount + " opening apps (frozen=" + "Checking " + apps.size() + " opening apps (frozen=" + mService.mDisplayFrozen + " timeout=" + mDisplayContent.mAppTransition.isTimeout() + ")..."); final ScreenRotationAnimation screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked( Display.DEFAULT_DISPLAY); - outReasons.clear(); if (!mDisplayContent.mAppTransition.isTimeout()) { // Imagine the case where we are changing orientation due to an app transition, but a // previous orientation change is still in progress. We won't process the orientation @@ -404,8 +457,8 @@ public class AppTransitionController { } return false; } - for (int i = 0; i < appsCount; i++) { - AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i); + for (int i = 0; i < apps.size(); i++) { + AppWindowToken wtoken = apps.valueAt(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Check opening app=" + wtoken + ": allDrawn=" + wtoken.allDrawn + " startingDisplayed=" diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java index bb38f3035a6c..ed029cd722cf 100644 --- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; + import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT; import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR; import static com.android.server.wm.AppWindowThumbnailProto.WIDTH; @@ -50,9 +53,23 @@ class AppWindowThumbnail implements Animatable { private final SurfaceAnimator mSurfaceAnimator; private final int mWidth; private final int mHeight; + private final boolean mRelative; AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) { + this(t, appToken, thumbnailHeader, false /* relative */); + } + + /** + * @param t Transaction to create the thumbnail in. + * @param appToken {@link AppWindowToken} to associate this thumbnail with. + * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with. + * @param relative Whether this thumbnail will be a child of appToken (and thus positioned + * relative to it) or not. + */ + AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader, + boolean relative) { mAppToken = appToken; + mRelative = relative; mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService); mWidth = thumbnailHeader.getWidth(); @@ -68,7 +85,8 @@ class AppWindowThumbnail implements Animatable { .setName("thumbnail anim: " + appToken.toString()) .setBufferSize(mWidth, mHeight) .setFormat(PixelFormat.TRANSLUCENT) - .setMetadata(appToken.windowType, + .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType) + .setMetadata(METADATA_OWNER_UID, window != null ? window.mOwnerUid : Binder.getCallingUid()) .build(); @@ -86,6 +104,9 @@ class AppWindowThumbnail implements Animatable { // We parent the thumbnail to the task, and just place it on top of anything else in the // task. t.setLayer(mSurfaceControl, Integer.MAX_VALUE); + if (relative) { + t.reparent(mSurfaceControl, appToken.getSurfaceControl()); + } } void startAnimation(Transaction t, Animation anim) { @@ -101,6 +122,13 @@ class AppWindowThumbnail implements Animatable { mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */); } + /** + * Start animation with existing adapter. + */ + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { + mSurfaceAnimator.startAnimation(t, anim, hidden); + } + private void onAnimationFinished() { } @@ -147,6 +175,9 @@ class AppWindowThumbnail implements Animatable { @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { t.setLayer(leash, Integer.MAX_VALUE); + if (mRelative) { + t.reparent(leash, mAppToken.getSurfaceControl()); + } } @Override diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 750c5ca5922e..a52f1af3b8b1 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -33,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_UNSET; import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; @@ -97,11 +99,13 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.IApplicationToken; import android.view.InputApplicationHandle; +import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -117,6 +121,7 @@ import com.android.server.LocalServices; import com.android.server.display.ColorDisplayService; import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.StartingSurface; +import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; @@ -261,7 +266,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ private boolean mLastSurfaceShowing = true; + /** + * This gets used during some open/close transitions as well as during a change transition + * where it represents the starting-state snapshot. + */ private AppWindowThumbnail mThumbnail; + private final Rect mTransitStartRect = new Rect(); + + /** + * This leash is used to "freeze" the app surface in place after the state change, but before + * the animation is ready to start. + */ + private SurfaceControl mTransitChangeLeash = null; /** Have we been asked to have this token keep the screen frozen? */ private boolean mFreezingScreen; @@ -272,6 +288,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private final Point mTmpPoint = new Point(); private final Rect mTmpRect = new Rect(); + private final Rect mTmpPrevBounds = new Rect(); private RemoteAnimationDefinition mRemoteAnimationDefinition; private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry; @@ -810,6 +827,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction); getDisplayContent().mOpeningApps.remove(this); + getDisplayContent().mChangingApps.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); mWmService.mTaskSnapshotController.onAppRemoved(this); waitingToShow = false; @@ -1528,6 +1546,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWinMode = getWindowingMode(); + mTmpPrevBounds.set(getBounds()); super.onConfigurationChanged(newParentConfig); final int winMode = getWindowingMode(); @@ -1559,7 +1578,82 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this, stackBounds); } + } else if (shouldStartChangeTransition(prevWinMode, winMode)) { + initializeChangeTransition(mTmpPrevBounds); + } + } + + private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { + if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) { + return false; } + // Only do an animation into and out-of freeform mode for now. Other mode + // transition animations are currently handled by system-ui. + return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM); + } + + /** + * Initializes a change transition. Because the app is visible already, there is a small period + * of time where the user can see the app content/window update before the transition starts. + * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which + * "freezes" the location/crop until the transition starts. + * <p> + * Here's a walk-through of the process: + * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it. + * 2. Set the temporary leash's position/crop to the current state. + * 3. Create a snapshot and place that at the top of the leash to cover up content changes. + * 4. Once the transition is ready, it will reparent the app to the animation leash. + * 5. Detach the interim-change-leash. + */ + private void initializeChangeTransition(Rect startBounds) { + mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE, + false /* alwaysKeepCurrent */, 0, false /* forceOverride */); + mDisplayContent.mChangingApps.add(this); + mTransitStartRect.set(startBounds); + + final SurfaceControl.Builder builder = makeAnimationLeash() + .setParent(getAnimationLeashParent()) + .setName(getSurfaceControl() + " - interim-change-leash"); + mTransitChangeLeash = builder.build(); + Transaction t = getPendingTransaction(); + t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height()); + t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top); + t.show(mTransitChangeLeash); + t.reparent(getSurfaceControl(), mTransitChangeLeash); + onAnimationLeashCreated(t, mTransitChangeLeash); + + // Skip creating snapshot if this transition is controlled by a remote animator which + // doesn't need it. + ArraySet<Integer> activityTypes = new ArraySet<>(); + activityTypes.add(getActivityType()); + RemoteAnimationAdapter adapter = + mDisplayContent.mAppTransitionController.getRemoteAnimationOverride( + this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes); + if (adapter != null && !adapter.getChangeNeedsSnapshot()) { + return; + } + + if (mThumbnail == null && getTask() != null) { + final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController; + final ArraySet<Task> tasks = new ArraySet<>(); + tasks.add(getTask()); + snapshotCtrl.snapshotTasks(tasks); + snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks); + final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot( + getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */, + false /* reducedResolution */); + mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(), + true /* relative */); + } + } + + boolean isInChangeTransition() { + return mTransitChangeLeash != null || isChangeTransition(mTransit); + } + + @VisibleForTesting + AppWindowThumbnail getThumbnail() { + return mThumbnail; } @Override @@ -2242,6 +2336,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return getBounds(); } + private static boolean isChangeTransition(int transit) { + return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE; + } + boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { @@ -2260,13 +2358,37 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked"); if (okToAnimate()) { final AnimationAdapter adapter; + AnimationAdapter thumbnailAdapter = null; getAnimationBounds(mTmpPoint, mTmpRect); + boolean isChanging = isChangeTransition(transit) && enter; + // Delaying animation start isn't compatible with remote animations at all. if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null && !mSurfaceAnimator.isAnimationStartDelayed()) { - adapter = getDisplayContent().mAppTransition.getRemoteAnimationController() - .createAnimationAdapter(this, mTmpPoint, mTmpRect); + RemoteAnimationRecord adapters = + getDisplayContent().mAppTransition.getRemoteAnimationController() + .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect, + (isChanging ? mTransitStartRect : null)); + adapter = adapters.mAdapter; + thumbnailAdapter = adapters.mThumbnailAdapter; + } else if (isChanging) { + final float durationScale = mWmService.getTransitionAnimationScaleLocked(); + mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); + adapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), durationScale, + true /* isAppAnimation */, false /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + if (mThumbnail != null) { + thumbnailAdapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), durationScale, + true /* isAppAnimation */, true /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + } + mTransit = transit; + mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); } else { final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode(); @@ -2294,6 +2416,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (adapter.getShowWallpaper()) { mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } + if (thumbnailAdapter != null) { + mThumbnail.startAnimation( + getPendingTransaction(), thumbnailAdapter, !isVisible()); + } } } else { cancelAnimation(); @@ -2429,6 +2555,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree final DisplayContent dc = getDisplayContent(); dc.assignStackOrdering(); + + if (leash == mTransitChangeLeash) { + // This is a temporary state so skip any animation notifications + return; + } else if (mTransitChangeLeash != null) { + // unparent mTransitChangeLeash for clean-up + t.hide(mTransitChangeLeash); + t.reparent(mTransitChangeLeash, null); + mTransitChangeLeash = null; + } + if (mAnimatingAppWindowTokenRegistry != null) { mAnimatingAppWindowTokenRegistry.notifyStarting(this); } @@ -2487,6 +2624,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree + " okToAnimate=" + okToAnimate() + " startingDisplayed=" + startingDisplayed); + // clean up thumbnail window + if (mThumbnail != null) { + mThumbnail.destroy(); + mThumbnail = null; + } + // WindowState.onExitAnimationDone might modify the children list, so make a copy and then // traverse the copy. final ArrayList<WindowState> children = new ArrayList<>(mChildren); @@ -2518,14 +2661,30 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override void cancelAnimation() { - super.cancelAnimation(); + cancelAnimationOnly(); clearThumbnail(); + if (mTransitChangeLeash != null) { + getPendingTransaction().hide(mTransitChangeLeash); + getPendingTransaction().reparent(mTransitChangeLeash, null); + mTransitChangeLeash = null; + } + } + + /** + * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail + * or interim leashes. + * <p> + * Used when canceling in preparation for starting a new animation. + */ + void cancelAnimationOnly() { + super.cancelAnimation(); } boolean isWaitingForTransitionStart() { return getDisplayContent().mAppTransition.isTransitionSet() && (getDisplayContent().mOpeningApps.contains(this) - || getDisplayContent().mClosingApps.contains(this)); + || getDisplayContent().mClosingApps.contains(this) + || getDisplayContent().mChangingApps.contains(this)); } public int getTransit() { @@ -2835,6 +2994,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void removeFromPendingTransition() { if (isWaitingForTransitionStart() && mDisplayContent != null) { mDisplayContent.mOpeningApps.remove(this); + mDisplayContent.mChangingApps.remove(this); mDisplayContent.mClosingApps.remove(this); } } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 650d0be76dc5..ce3fb51aa9f5 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -41,6 +41,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.util.ArrayList; @@ -522,6 +524,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mChangeListeners.remove(listener); } + @VisibleForTesting + boolean containsListener(ConfigurationContainerListener listener) { + return mChangeListeners.contains(listener); + } + /** * Must be called when new parent for the container was set. */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8fefd352e027..7a9ff527fc08 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -162,6 +162,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InsetsState.InternalInsetType; import android.view.MagnificationSpec; +import android.view.RemoteAnimationDefinition; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -250,6 +251,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>(); final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>(); + final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; BoundsAnimationController mBoundsAnimationController; @@ -1055,11 +1057,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ void setInsetProvider(@InternalInsetType int type, WindowState win, @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) { - if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) { - if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) { - return; - } - } mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider); } @@ -1090,6 +1087,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mLastWindowForcedOrientation; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mAppTransitionController.registerRemoteAnimations(definition); + } + /** * Temporarily pauses rotation changes until resumed. * @@ -2454,6 +2455,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Clear all transitions & screen frozen states when removing display. mOpeningApps.clear(); mClosingApps.clear(); + mChangingApps.clear(); mUnknownAppVisibilityController.clear(); mAppTransition.removeAppTransitionTimeoutCallbacks(); handleAnimatingStoppedAndTransition(); @@ -3284,7 +3286,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) { + if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) { pw.println(); if (mOpeningApps.size() > 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); @@ -3292,6 +3294,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (mClosingApps.size() > 0) { pw.print(" mClosingApps="); pw.println(mClosingApps); } + if (mChangingApps.size() > 0) { + pw.print(" mChangingApps="); pw.println(mChangingApps); + } } mUnknownAppVisibilityController.dump(pw, " "); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 6d3c69385a09..40063326e76e 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -58,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLES import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -2029,7 +2030,8 @@ public class DisplayPolicy { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); - } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) { + } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT + || type == TYPE_APPLICATION_OVERLAY) { // These dialogs are stable to interim decor changes. cf.set(displayFrames.mStable); of.set(displayFrames.mStable); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 7ea88bbdaec8..ea65dd99077a 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -36,7 +36,6 @@ import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED; import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; import android.content.Context; @@ -566,9 +565,7 @@ public class DockedStackDividerController { mMaximizeMeetFraction = getClipRevealMeetFraction(stack); animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction); } - mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED); - mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED, - minimizedDock ? 1 : 0, 0).sendToTarget(); + mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock); final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 632db3842839..3c5d911903e7 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -46,7 +46,6 @@ import android.view.InputChannel; import android.view.InputEventReceiver; import android.view.InputWindowHandle; import android.view.SurfaceControl; -import android.view.animation.Animation; import com.android.server.AnimationThread; import com.android.server.policy.WindowManagerPolicy; @@ -70,8 +69,7 @@ final class InputMonitor { private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); - private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer = - new UpdateInputForAllWindowsConsumer(); + private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer; private final int mDisplayId; private final DisplayContent mDisplayContent; @@ -165,6 +163,7 @@ final class InputMonitor { mDisplayId = displayId; mInputTransaction = mDisplayContent.getPendingTransaction(); mHandler = AnimationThread.getHandler(); + mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer(); } void onDisplayRemoved() { @@ -407,6 +406,9 @@ final class InputMonitor { boolean inDrag; WallpaperController wallpaperController; + // An invalid window handle that tells SurfaceFlinger not update the input info. + final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId); + private void updateInputWindows(boolean inDrag) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); @@ -445,6 +447,10 @@ final class InputMonitor { final InputWindowHandle inputWindowHandle = w.mInputWindowHandle; if (inputChannel == null || inputWindowHandle == null || w.mRemoved || w.cantReceiveTouchInput()) { + if (w.mWinAnimator.hasSurface()) { + mInputTransaction.setInputWindowInfo( + w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow); + } // Skip this window because it cannot possibly receive input. return; } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e49e4c0711bd..e79820338c55 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -16,7 +16,13 @@ package com.android.server.wm; +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; import android.annotation.NonNull; import android.annotation.Nullable; @@ -59,6 +65,7 @@ class InsetsSourceProvider { */ private boolean mServerVisible; + private final boolean mControllable; InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent) { @@ -66,6 +73,15 @@ class InsetsSourceProvider { mSource = source; mDisplayContent = displayContent; mStateController = stateController; + + final int type = source.getType(); + if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) { + mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL; + } else if (type == TYPE_IME) { + mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME; + } else { + mControllable = false; + } } InsetsSource getSource() { @@ -73,6 +89,13 @@ class InsetsSourceProvider { } /** + * @return Whether the current flag configuration allows to control this source. + */ + boolean isControllable() { + return mControllable; + } + + /** * Updates the window that currently backs this source. * * @param win The window that links to this source. @@ -91,6 +114,9 @@ class InsetsSourceProvider { mSource.setFrame(new Rect()); } else { mWin.setInsetProvider(this); + if (mControllingWin != null) { + updateControlForTarget(mControllingWin, true /* force */); + } } } @@ -113,8 +139,12 @@ class InsetsSourceProvider { && !mWin.mGivenInsetsPending); } - void updateControlForTarget(@Nullable WindowState target) { - if (target == mControllingWin) { + void updateControlForTarget(@Nullable WindowState target, boolean force) { + if (mWin == null) { + mControllingWin = target; + return; + } + if (target == mControllingWin && !force) { return; } if (target == null) { @@ -161,7 +191,7 @@ class InsetsSourceProvider { } boolean isClientVisible() { - return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible; + return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible; } private class ControlAdapter implements AnimationAdapter { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 32dbe96d39f7..bb0cbb1de470 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -163,18 +163,18 @@ class InsetsStateController { } private void onControlChanged(int type, @Nullable WindowState win) { - if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { - return; - } final WindowState previous = mTypeWinControlMap.get(type); if (win == previous) { return; } - final InsetsSourceProvider controller = mControllers.get(type); + final InsetsSourceProvider controller = getSourceProvider(type); if (controller == null) { return; } - controller.updateControlForTarget(win); + if (!controller.isControllable()) { + return; + } + controller.updateControlForTarget(win, false /* force */); if (previous != null) { removeFromControlMaps(previous, type); mPendingControlChanged.add(previous); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 177f244129f4..b5be2ac5df98 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -91,9 +91,7 @@ class KeyguardController { */ boolean isKeyguardOrAodShowing(int displayId) { return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway - && (displayId == DEFAULT_DISPLAY - ? !isDisplayOccluded(DEFAULT_DISPLAY) - : isShowingOnSecondaryDisplay(displayId)); + && !isDisplayOccluded(displayId); } /** @@ -101,10 +99,7 @@ class KeyguardController { * display, false otherwise */ boolean isKeyguardShowing(int displayId) { - return mKeyguardShowing && !mKeyguardGoingAway - && (displayId == DEFAULT_DISPLAY - ? !isDisplayOccluded(DEFAULT_DISPLAY) - : isShowingOnSecondaryDisplay(displayId)); + return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId); } /** @@ -152,14 +147,6 @@ class KeyguardController { updateKeyguardSleepToken(); } - private boolean isShowingOnSecondaryDisplay(int displayId) { - if (mSecondaryDisplayIdsShowing == null) return false; - for (int showingId : mSecondaryDisplayIdsShowing) { - if (displayId == showingId) return true; - } - return false; - } - /** * Called when Keyguard is going away. * @@ -473,8 +460,16 @@ class KeyguardController { if (stack.getTopDismissingKeyguardActivity() != null) { mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity(); } + // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display. + if (mDisplayId != DEFAULT_DISPLAY) { + mOccluded |= stack.canShowWithInsecureKeyguard() + && controller.canDismissKeyguard(); + } + } + // TODO(b/123372519): isShowingDream can only works on default display. + if (mDisplayId == DEFAULT_DISPLAY) { + mOccluded |= controller.mWindowManager.isShowingDream(); } - mOccluded |= controller.mWindowManager.isShowingDream(); // TODO(b/113840485): Handle app transition for individual display, and apply occluded // state change to secondary displays. diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 83ba384c9069..105ff0674ef0 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -29,7 +29,6 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_R import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS; -import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING; import android.annotation.IntDef; import android.app.ActivityManager.TaskSnapshot; @@ -413,8 +412,7 @@ public class RecentsAnimationController implements DeathRecipient { } final SparseIntArray reasons = new SparseIntArray(); reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM); - mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, - reasons).sendToTarget(); + mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis()); } void cancelAnimation(@ReorderMode int reorderMode, String reason) { @@ -626,7 +624,7 @@ public class RecentsAnimationController implements DeathRecipient { mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, mTask.getPrefixOrderIndex(), mPosition, mBounds, - mTask.getWindowConfiguration(), mIsRecentTaskInvisible); + mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null); return mTarget; } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index f5acdccf07f4..5f95691d5307 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -57,7 +57,7 @@ class RemoteAnimationController implements DeathRecipient { private final WindowManagerService mService; private final RemoteAnimationAdapter mRemoteAnimationAdapter; - private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); + private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>(); private final Rect mTmpRect = new Rect(); private final Handler mHandler; private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); @@ -74,21 +74,22 @@ class RemoteAnimationController implements DeathRecipient { } /** - * Creates an animation for each individual {@link AppWindowToken}. + * Creates an animation record for each individual {@link AppWindowToken}. * * @param appWindowToken The app to animate. * @param position The position app bounds, in screen coordinates. - * @param stackBounds The stack bounds of the app. - * @return The adapter to be run on the app. + * @param stackBounds The stack bounds of the app relative to position. + * @param startBounds The stack bounds before the transition, in screen coordinates + * @return The record representing animation(s) to run on the app. */ - AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position, - Rect stackBounds) { + RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken, + Point position, Rect stackBounds, Rect startBounds) { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token=" + appWindowToken); - final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper( - appWindowToken, position, stackBounds); - mPendingAnimations.add(adapter); - return adapter; + final RemoteAnimationRecord adapters = + new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds); + mPendingAnimations.add(adapters); + return adapters; } /** @@ -148,7 +149,7 @@ class RemoteAnimationController implements DeathRecipient { final StringWriter sw = new StringWriter(); final FastPrintWriter pw = new FastPrintWriter(sw); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - mPendingAnimations.get(i).dump(pw, ""); + mPendingAnimations.get(i).mAdapter.dump(pw, ""); } pw.close(); Slog.i(TAG, sw.toString()); @@ -158,19 +159,26 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()"); final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i); - final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation(); + final RemoteAnimationRecord wrappers = mPendingAnimations.get(i); + final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget(); if (target != null) { - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken); + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken); targets.add(target); } else { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token=" - + wrapper.mAppWindowToken); + + wrappers.mAppWindowToken); // We can't really start an animation but we still need to make sure to finish the // pending animation that was started by SurfaceAnimator - if (wrapper.mCapturedFinishCallback != null) { - wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper); + if (wrappers.mAdapter != null + && wrappers.mAdapter.mCapturedFinishCallback != null) { + wrappers.mAdapter.mCapturedFinishCallback + .onAnimationFinished(wrappers.mAdapter); + } + if (wrappers.mThumbnailAdapter != null + && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) { + wrappers.mThumbnailAdapter.mCapturedFinishCallback + .onAnimationFinished(wrappers.mThumbnailAdapter); } mPendingAnimations.remove(i); } @@ -190,9 +198,16 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): Notify animation finished:"); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i); - adapter.mCapturedFinishCallback.onAnimationFinished(adapter); - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken); + final RemoteAnimationRecord adapters = mPendingAnimations.get(i); + if (adapters.mAdapter != null) { + adapters.mAdapter.mCapturedFinishCallback + .onAnimationFinished(adapters.mAdapter); + } + if (adapters.mThumbnailAdapter != null) { + adapters.mThumbnailAdapter.mCapturedFinishCallback + .onAnimationFinished(adapters.mThumbnailAdapter); + } + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken); } } catch (Exception e) { Slog.e(TAG, "Failed to finish remote animation", e); @@ -282,47 +297,86 @@ class RemoteAnimationController implements DeathRecipient { } }; - private class RemoteAnimationAdapterWrapper implements AnimationAdapter { - - private final AppWindowToken mAppWindowToken; - private SurfaceControl mCapturedLeash; - private OnAnimationFinishedCallback mCapturedFinishCallback; - private final Point mPosition = new Point(); - private final Rect mStackBounds = new Rect(); - private RemoteAnimationTarget mTarget; - - RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position, - Rect stackBounds) { + /** + * Contains information about a remote-animation for one AppWindowToken. This keeps track of, + * potentially, multiple animating surfaces (AdapterWrappers) associated with one + * Window/Transition. For example, a change transition has an adapter controller for the + * main window and an adapter controlling the start-state snapshot. + * <p> + * This can be thought of as a bridge between the information that the remote animator sees (via + * {@link RemoteAnimationTarget}) and what the server sees (the + * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces). + */ + public class RemoteAnimationRecord { + RemoteAnimationAdapterWrapper mAdapter; + RemoteAnimationAdapterWrapper mThumbnailAdapter = null; + RemoteAnimationTarget mTarget; + final AppWindowToken mAppWindowToken; + final Rect mStartBounds; + + RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds, + Rect startBounds) { mAppWindowToken = appWindowToken; - mPosition.set(position.x, position.y); - mStackBounds.set(stackBounds); + mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds); + if (startBounds != null) { + mStartBounds = new Rect(startBounds); + mTmpRect.set(startBounds); + mTmpRect.offsetTo(0, 0); + if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { + mThumbnailAdapter = + new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect); + } + } else { + mStartBounds = null; + } } - RemoteAnimationTarget createRemoteAppAnimation() { + RemoteAnimationTarget createRemoteAnimationTarget() { final Task task = mAppWindowToken.getTask(); final WindowState mainWindow = mAppWindowToken.findMainWindow(); - if (task == null || mainWindow == null || mCapturedFinishCallback == null - || mCapturedLeash == null) { + if (task == null || mainWindow == null || mAdapter == null + || mAdapter.mCapturedFinishCallback == null + || mAdapter.mCapturedLeash == null) { return null; } final Rect insets = new Rect(); mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets()); mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(), - mCapturedLeash, !mAppWindowToken.fillsParent(), + mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, - mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds, - task.getWindowConfiguration(), false /*isNotInRecents*/); + mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition, + mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/, + mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null, + mStartBounds); return mTarget; } private int getMode() { - if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) { + final DisplayContent dc = mAppWindowToken.getDisplayContent(); + if (dc.mOpeningApps.contains(mAppWindowToken)) { return RemoteAnimationTarget.MODE_OPENING; + } else if (dc.mChangingApps.contains(mAppWindowToken)) { + return RemoteAnimationTarget.MODE_CHANGING; } else { return RemoteAnimationTarget.MODE_CLOSING; } } + } + + private class RemoteAnimationAdapterWrapper implements AnimationAdapter { + private final RemoteAnimationRecord mRecord; + SurfaceControl mCapturedLeash; + private OnAnimationFinishedCallback mCapturedFinishCallback; + private final Point mPosition = new Point(); + private final Rect mStackBounds = new Rect(); + + RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, + Rect stackBounds) { + mRecord = record; + mPosition.set(position.x, position.y); + mStackBounds.set(stackBounds); + } @Override public boolean getShowWallpaper() { @@ -340,7 +394,7 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation"); // Restore z-layering, position and stack crop until client has a chance to modify it. - t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex()); + t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex()); t.setPosition(animationLeash, mPosition.x, mPosition.y); mTmpRect.set(mStackBounds); mTmpRect.offsetTo(0, 0); @@ -351,7 +405,14 @@ class RemoteAnimationController implements DeathRecipient { @Override public void onAnimationCancelled(SurfaceControl animationLeash) { - mPendingAnimations.remove(this); + if (mRecord.mAdapter == this) { + mRecord.mAdapter = null; + } else { + mRecord.mThumbnailAdapter = null; + } + if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) { + mPendingAnimations.remove(mRecord); + } if (mPendingAnimations.isEmpty()) { mHandler.removeCallbacks(mTimeoutRunnable); releaseFinishedCallback(); @@ -373,10 +434,10 @@ class RemoteAnimationController implements DeathRecipient { @Override public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken); - if (mTarget != null) { + pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken); + if (mRecord.mTarget != null) { pw.print(prefix); pw.println("Target:"); - mTarget.dump(pw, prefix + " "); + mRecord.mTarget.dump(pw, prefix + " "); } else { pw.print(prefix); pw.println("Target: null"); } @@ -385,8 +446,8 @@ class RemoteAnimationController implements DeathRecipient { @Override public void writeToProto(ProtoOutputStream proto) { final long token = proto.start(REMOTE); - if (mTarget != null) { - mTarget.writeToProto(proto, TARGET); + if (mRecord.mTarget != null) { + mRecord.mTarget.writeToProto(proto, TARGET); } proto.end(token); } diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java index b4de75b287fa..6db606d2a30b 100644 --- a/services/core/java/com/android/server/wm/StatusBarController.java +++ b/services/core/java/com/android/server/wm/StatusBarController.java @@ -61,9 +61,8 @@ public class StatusBarController extends BarController { } @Override - public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); if (statusBar != null && mWin != null) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a7dd55b8a160..476bd6e9abe9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; +import static android.view.SurfaceControl.METADATA_TASK_ID; import static com.android.server.EventLogTags.WM_TASK_REMOVED; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; @@ -643,6 +644,11 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta return getAppAnimationLayer(ANIMATION_LAYER_HOME); } + @Override + SurfaceControl.Builder makeSurface() { + return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId); + } + boolean isTaskAnimating() { final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController(); if (recentsAnim != null) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 15239c7e57e1..c15afc547085 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -661,7 +661,8 @@ class WallpaperController { * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of * the opening apps should be a wallpaper target. */ - void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) { + void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps, + ArraySet<AppWindowToken> changingApps) { boolean adjust = false; if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) { adjust = true; @@ -673,6 +674,15 @@ class WallpaperController { break; } } + if (!adjust) { + for (int i = changingApps.size() - 1; i >= 0; --i) { + final AppWindowToken token = changingApps.valueAt(i); + if (token.windowsCanBeWallpaperTarget()) { + adjust = true; + break; + } + } + } } if (adjust) { diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java new file mode 100644 index 000000000000..775d5b2fb79a --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 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.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION; +import static com.android.server.wm.AnimationSpecProto.WINDOW; +import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.SystemClock; +import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.ClipRectAnimation; +import android.view.animation.ScaleAnimation; +import android.view.animation.Transformation; +import android.view.animation.TranslateAnimation; + +import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; + +import java.io.PrintWriter; + +/** + * Animation spec for changing window animations. + */ +public class WindowChangeAnimationSpec implements AnimationSpec { + + private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new); + private final boolean mIsAppAnimation; + private final Rect mStartBounds; + private final Rect mEndBounds; + private final Rect mTmpRect = new Rect(); + + private Animation mAnimation; + private final boolean mIsThumbnail; + + static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION; + + public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo, + float durationScale, boolean isAppAnimation, boolean isThumbnail) { + mStartBounds = new Rect(startBounds); + mEndBounds = new Rect(endBounds); + mIsAppAnimation = isAppAnimation; + mIsThumbnail = isThumbnail; + createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo); + } + + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public long getDuration() { + return mAnimation.getDuration(); + } + + /** + * This animator behaves slightly differently depending on whether the window is growing + * or shrinking: + * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old) + * snapshot. + * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker + * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into + * place. + * @param duration + * @param displayInfo + */ + private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) { + boolean growing = mEndBounds.width() - mStartBounds.width() + + mEndBounds.height() - mStartBounds.height() >= 0; + float scalePart = 0.7f; + long scalePeriod = (long) (duration * scalePart); + float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width() + + (1.f - scalePart); + float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height() + + (1.f - scalePart); + if (mIsThumbnail) { + AnimationSet animSet = new AnimationSet(true); + Animation anim = new AlphaAnimation(1.f, 0.f); + anim.setDuration(scalePeriod); + if (!growing) { + anim.setStartOffset(duration - scalePeriod); + } + animSet.addAnimation(anim); + float endScaleX = 1.f / startScaleX; + float endScaleY = 1.f / startScaleY; + anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY); + anim.setDuration(duration); + animSet.addAnimation(anim); + mAnimation = animSet; + mAnimation.initialize(mStartBounds.width(), mStartBounds.height(), + mEndBounds.width(), mEndBounds.height()); + } else { + AnimationSet animSet = new AnimationSet(true); + final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1); + scaleAnim.setDuration(scalePeriod); + if (!growing) { + scaleAnim.setStartOffset(duration - scalePeriod); + } + animSet.addAnimation(scaleAnim); + final Animation translateAnim = new TranslateAnimation(mStartBounds.left, + mEndBounds.left, mStartBounds.top, mEndBounds.top); + translateAnim.setDuration(duration); + animSet.addAnimation(translateAnim); + Rect startClip = new Rect(mStartBounds); + Rect endClip = new Rect(mEndBounds); + startClip.offsetTo(0, 0); + endClip.offsetTo(0, 0); + final Animation clipAnim = new ClipRectAnimation(startClip, endClip); + clipAnim.setDuration(duration); + animSet.addAnimation(clipAnim); + mAnimation = animSet; + mAnimation.initialize(mStartBounds.width(), mStartBounds.height(), + displayInfo.appWidth, displayInfo.appHeight); + } + } + + @Override + public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { + final TmpValues tmp = mThreadLocalTmps.get(); + if (mIsThumbnail) { + mAnimation.getTransformation(currentPlayTime, tmp.mTransformation); + t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats); + t.setAlpha(leash, tmp.mTransformation.getAlpha()); + } else { + mAnimation.getTransformation(currentPlayTime, tmp.mTransformation); + final Matrix matrix = tmp.mTransformation.getMatrix(); + t.setMatrix(leash, matrix, tmp.mFloats); + + // The following applies an inverse scale to the clip-rect so that it crops "after" the + // scale instead of before. + tmp.mVecs[1] = tmp.mVecs[2] = 0; + tmp.mVecs[0] = tmp.mVecs[3] = 1; + matrix.mapVectors(tmp.mVecs); + tmp.mVecs[0] = 1.f / tmp.mVecs[0]; + tmp.mVecs[3] = 1.f / tmp.mVecs[3]; + final Rect clipRect = tmp.mTransformation.getClipRect(); + mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f); + mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f); + mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f); + mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f); + t.setWindowCrop(leash, mTmpRect); + } + } + + @Override + public long calculateStatusBarTransitionStartTime() { + long uptime = SystemClock.uptimeMillis(); + return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f)) + - STATUS_BAR_TRANSITION_DURATION); + } + + @Override + public boolean canSkipFirstFrame() { + return false; + } + + @Override + public boolean needsEarlyWakeup() { + return mIsAppAnimation; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.println(mAnimation.getDuration()); + } + + @Override + public void writeToProtoInner(ProtoOutputStream proto) { + final long token = proto.start(WINDOW); + proto.write(ANIMATION, mAnimation.toString()); + proto.end(token); + } + + private static class TmpValues { + final Transformation mTransformation = new Transformation(); + final float[] mFloats = new float[9]; + final float[] mVecs = new float[4]; + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 5267e7e55793..e204697e46cf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -118,8 +118,6 @@ public abstract class WindowManagerInternal { * * @param transit transition type indicating what kind of transition gets run, must be one * of AppTransition.TRANSIT_* values - * @param openToken the token for the opening app - * @param closeToken the token for the closing app * @param duration the total duration of the transition * @param statusBarAnimationStartTime the desired start time for all visual animations in * the status bar caused by this app transition in uptime millis @@ -131,8 +129,8 @@ public abstract class WindowManagerInternal { * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ - public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken, - long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { return 0; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c6679a9ad0d7..e6581df233ef 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -184,7 +184,6 @@ import android.util.MergedConfiguration; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.TypedValue; import android.util.proto.ProtoOutputStream; @@ -857,12 +856,12 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void onAppTransitionCancelledLocked(int transit) { - mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED); + mAtmInternal.notifyAppTransitionCancelled(); } @Override public void onAppTransitionFinishedLocked(IBinder token) { - mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED); + mAtmInternal.notifyAppTransitionFinished(); final AppWindowToken atoken = mRoot.getAppWindowToken(token); if (atoken == null) { return; @@ -2602,7 +2601,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void notifyKeyguardTrustedChanged() { - mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED); + mAtmInternal.notifyKeyguardTrustedChanged(); } @Override @@ -2667,11 +2666,7 @@ public class WindowManagerService extends IWindowManager.Stub * @param callback Runnable to be called when activity manager is done reevaluating visibilities */ void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) { - final Runnable wrappedCallback = callback != null - ? () -> { synchronized (mGlobalLock) { callback.run(); } } - : null; - mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, displayId, 0, - wrappedCallback).sendToTarget(); + mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId); } public boolean isKeyguardTrusted() { @@ -2822,7 +2817,7 @@ public class WindowManagerService extends IWindowManager.Stub public boolean isShowingDream() { synchronized (mGlobalLock) { - // TODO: fix this when dream can be shown on non-default display. + // TODO(b/123372519): Fix this when dream can be shown on non-default display. return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw(); } } @@ -4363,16 +4358,10 @@ public class WindowManagerService extends IWindowManager.Stub public static final int WINDOW_REPLACEMENT_TIMEOUT = 46; - public static final int NOTIFY_APP_TRANSITION_STARTING = 47; - public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48; - public static final int NOTIFY_APP_TRANSITION_FINISHED = 49; public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; - public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53; public static final int SEAMLESS_ROTATION_TIMEOUT = 54; public static final int RESTORE_POINTER_ICON = 55; - public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56; - public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; public static final int SET_RUNNING_REMOTE_ANIMATION = 59; public static final int ANIMATION_FAILSAFE = 60; @@ -4708,19 +4697,6 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case NOTIFY_APP_TRANSITION_STARTING: { - mAtmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj, - msg.getWhen()); - } - break; - case NOTIFY_APP_TRANSITION_CANCELLED: { - mAtmInternal.notifyAppTransitionCancelled(); - } - break; - case NOTIFY_APP_TRANSITION_FINISHED: { - mAtmInternal.notifyAppTransitionFinished(); - } - break; case WINDOW_HIDE_TIMEOUT: { final WindowState window = (WindowState) msg.obj; synchronized (mGlobalLock) { @@ -4742,10 +4718,6 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED: { - mAtmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1); - } - break; case RESTORE_POINTER_ICON: { synchronized (mGlobalLock) { restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2); @@ -4759,14 +4731,6 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case NOTIFY_KEYGUARD_FLAGS_CHANGED: { - mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj, msg.arg1); - } - break; - case NOTIFY_KEYGUARD_TRUSTED_CHANGED: { - mAtmInternal.notifyKeyguardTrustedChanged(); - } - break; case SET_HAS_OVERLAY_UI: { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index c38a974ac774..07f26b4c70bb 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -52,6 +52,7 @@ import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.Watchdog; @@ -330,6 +331,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mRequiredAbi; } + /** Returns ID of display overriding the configuration for this process, or + * INVALID_DISPLAY if no display is overriding. */ + @VisibleForTesting + int getDisplayId() { + return mDisplayId; + } + public void setDebugging(boolean debugging) { mDebugging = debugging; } @@ -761,15 +769,15 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio activityDisplay.registerConfigurationChangeListener(this); } - private void unregisterDisplayConfigurationListenerLocked() { + @VisibleForTesting + void unregisterDisplayConfigurationListenerLocked() { if (mDisplayId == INVALID_DISPLAY) { return; } final ActivityDisplay activityDisplay = mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId); if (activityDisplay != null) { - mAtm.mRootActivityContainer.getActivityDisplay( - mDisplayId).unregisterConfigurationChangeListener(this); + activityDisplay.unregisterConfigurationChangeListener(this); } mDisplayId = INVALID_DISPLAY; } @@ -867,6 +875,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public boolean appNotResponding(String info, Runnable killAppCallback, Runnable serviceTimeoutCallback) { + Runnable targetRunnable = null; synchronized (mAtm.mGlobalLock) { if (mAtm.mController == null) { return false; @@ -877,18 +886,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int res = mAtm.mController.appNotResponding(mName, mPid, info); if (res != 0) { if (res < 0 && mPid != MY_PID) { - killAppCallback.run(); + targetRunnable = killAppCallback; } else { - serviceTimeoutCallback.run(); + targetRunnable = serviceTimeoutCallback; } - return true; } } catch (RemoteException e) { mAtm.mController = null; Watchdog.getInstance().setActivityController(null); + return false; } - return false; } + if (targetRunnable != null) { + targetRunnable.run(); + return true; + } + return false; } public void onTopProcChanged() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ce5eb8476764..4f120100693a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4367,6 +4367,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void startAnimation(Animation anim) { + + // If we are an inset provider, all our animations are driven by the inset client. + if (mInsetProvider != null && mInsetProvider.isControllable()) { + return; + } + final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo(); anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(), displayInfo.appWidth, displayInfo.appHeight); @@ -4380,6 +4386,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } private void startMoveAnimation(int left, int top) { + + // If we are an inset provider, all our animations are driven by the inset client. + if (mInsetProvider != null && mInsetProvider.isControllable()) { + return; + } + if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this); final Point oldPosition = new Point(); final Point newPosition = new Point(); diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index c2a8e7efb5a5..dea3597989be 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -35,7 +37,6 @@ import android.os.IBinder; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowContentFrameStats; @@ -103,7 +104,8 @@ class WindowSurfaceController { .setBufferSize(w, h) .setFormat(format) .setFlags(flags) - .setMetadata(windowType, ownerUid); + .setMetadata(METADATA_WINDOW_TYPE, windowType) + .setMetadata(METADATA_OWNER_UID, ownerUid); mSurfaceControl = b.build(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 90c9cc2b0a35..ff0b0d6f6eaf 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -782,7 +782,7 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleO } sp<InputWindowHandle> windowHandle = - android_server_InputWindowHandle_getHandle(env, windowHandleObj); + android_view_InputWindowHandle_getHandle(env, windowHandleObj); if (windowHandle != nullptr) { windowHandles.push(windowHandle); } @@ -822,7 +822,7 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleO void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj) { sp<InputApplicationHandle> applicationHandle = - android_server_InputApplicationHandle_getHandle(env, applicationHandleObj); + android_view_InputApplicationHandle_getHandle(env, applicationHandleObj); mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle); } diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp index c90113f4b44e..26f6d7428fcc 100644 --- a/services/core/jni/com_android_server_lights_LightsService.cpp +++ b/services/core/jni/com_android_server_lights_LightsService.cpp @@ -39,37 +39,7 @@ using Type = ::android::hardware::light::V2_0::Type; template<typename T> using Return = ::android::hardware::Return<T>; -class LightHal { -private: - static sp<ILight> sLight; - static bool sLightInit; - - LightHal() {} - -public: - static void disassociate() { - sLightInit = false; - sLight = nullptr; - } - - static sp<ILight> associate() { - if ((sLight == nullptr && !sLightInit) || - (sLight != nullptr && !sLight->ping().isOk())) { - // will return the hal if it exists the first time. - sLight = ILight::getService(); - sLightInit = true; - - if (sLight == nullptr) { - ALOGE("Unable to get ILight interface."); - } - } - - return sLight; - } -}; - -sp<ILight> LightHal::sLight = nullptr; -bool LightHal::sLightInit = false; +static bool sLightSupported = true; static bool validate(jint light, jint flash, jint brightness) { bool valid = true; @@ -134,7 +104,6 @@ static void processReturn( const LightState &state) { if (!ret.isOk()) { ALOGE("Failed to issue set light command."); - LightHal::disassociate(); return; } @@ -164,13 +133,11 @@ static void setLight_native( jint offMS, jint brightnessMode) { - if (!validate(light, flashMode, brightnessMode)) { + if (!sLightSupported) { return; } - sp<ILight> hal = LightHal::associate(); - - if (hal == nullptr) { + if (!validate(light, flashMode, brightnessMode)) { return; } @@ -180,6 +147,11 @@ static void setLight_native( { android::base::Timer t; + sp<ILight> hal = ILight::getService(); + if (hal == nullptr) { + sLightSupported = false; + return; + } Return<Status> ret = hal->setLight(type, state); processReturn(ret, type, state); if (t.duration() > 50ms) ALOGD("Excessive delay setting light"); diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 918f57e2945e..6e31aedd3f76 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -26,8 +26,6 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryStatsService(JNIEnv* env); int register_android_server_ConsumerIrService(JNIEnv *env); -int register_android_server_InputApplicationHandle(JNIEnv* env); -int register_android_server_InputWindowHandle(JNIEnv* env); int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_PowerManagerService(JNIEnv* env); @@ -74,8 +72,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_broadcastradio_Tuner(vm, env); register_android_server_PowerManagerService(env); register_android_server_SerialService(env); - register_android_server_InputApplicationHandle(env); - register_android_server_InputWindowHandle(env); register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index d8225b38487c..2bf6f357bec8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -108,12 +108,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {} @Override - public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) { - } - - @Override - public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) { - return false; + public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) { } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0977323754f9..f79f9bc4ef86 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -70,9 +70,11 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; +import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; + import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; @@ -81,10 +83,11 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent .PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager - .ADMIN_TYPE_DEVICE_OWNER; -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager - .ADMIN_TYPE_PROFILE_OWNER; + +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; + + import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -237,11 +240,11 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.JournaledFile; import com.android.internal.util.Preconditions; -import com.android.internal.util.StatLogger; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.internal.util.StatLogger; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; @@ -949,8 +952,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "metered_data_disabled_packages"; private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES = "cross-profile-calendar-packages"; - private static final String TAG_PACKAGE = "package"; - + private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL = + "cross-profile-calendar-packages-null"; DeviceAdminInfo info; @@ -1072,7 +1075,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String endUserSessionMessage = null; // The whitelist of packages that can access cross profile calendar APIs. - final Set<String> mCrossProfileCalendarPackages = new ArraySet<>(); + // This whitelist should be in default an empty list, which indicates that no package + // is whitelisted. + List<String> mCrossProfileCalendarPackages = Collections.emptyList(); ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; @@ -1343,11 +1348,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.text(endUserSessionMessage); out.endTag(null, TAG_END_USER_SESSION_MESSAGE); } - if (!mCrossProfileCalendarPackages.isEmpty()) { - out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES); - writeAttributeValuesToXml( - out, TAG_PACKAGE, mCrossProfileCalendarPackages); - out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES); + if (mCrossProfileCalendarPackages == null) { + out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL); + out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL); + } else { + writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES, + mCrossProfileCalendarPackages); } } @@ -1542,8 +1548,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Log.w(LOG_TAG, "Missing text when loading end session message"); } } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) { - readAttributeValues( - parser, TAG_PACKAGE, mCrossProfileCalendarPackages); + mCrossProfileCalendarPackages = readPackageList(parser, tag); + } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) { + mCrossProfileCalendarPackages = null; } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -1759,8 +1766,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.print(prefix); pw.println("parentAdmin:"); parentAdmin.dump(prefix + " ", pw); } - pw.print(prefix); pw.print("mCrossProfileCalendarPackages="); - pw.println(mCrossProfileCalendarPackages); + if (mCrossProfileCalendarPackages != null) { + pw.print(prefix); pw.print("mCrossProfileCalendarPackages="); + pw.println(mCrossProfileCalendarPackages); + } } } @@ -4223,7 +4232,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4246,13 +4255,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } return getStrictestPasswordRequirement(who, userHandle, parent, admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED); } @Override public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4288,7 +4300,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4423,7 +4435,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4770,6 +4782,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { if (!isCallerWithSystemUid()) { @@ -4789,7 +4804,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4815,7 +4830,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0; } enforceFullCrossUsersPermission(userHandle); @@ -4829,7 +4844,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return UserHandle.USER_NULL; } enforceFullCrossUsersPermission(userHandle); @@ -4910,6 +4925,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen"); + return false; + } + final int callingUid = mInjector.binderGetCallingUid(); final int userHandle = mInjector.userHandleGetCallingUserId(); @@ -5252,7 +5272,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -5285,7 +5305,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS; } enforceFullCrossUsersPermission(userId); @@ -6351,7 +6371,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void forceWipeUser(int userId, String wipeReasonForUser) { + private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) { boolean success = false; try { IActivityManager am = mInjector.getIActivityManager(); @@ -6362,7 +6382,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { success = mUserManagerInternal.removeUserEvenWhenDisallowed(userId); if (!success) { Slog.w(LOG_TAG, "Couldn't remove user " + userId); - } else if (isManagedProfile(userId) && !TextUtils.isEmpty(wipeReasonForUser)) { + } else if (isManagedProfile(userId) && !wipeSilently) { sendWipeProfileNotification(wipeReasonForUser); } } catch (RemoteException re) { @@ -6377,6 +6397,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } + Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty"); enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId()); final ActiveAdmin admin; @@ -6436,7 +6457,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { internalReason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0); } else { - forceWipeUser(userId, wipeReasonForUser); + forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0); } } finally { mInjector.binderRestoreCallingIdentity(ident); @@ -6494,7 +6515,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userHandle); @@ -6514,7 +6535,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void reportPasswordChanged(@UserIdInt int userId) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userId); @@ -7656,7 +7677,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // Shutting down backup manager service permanently. - toggleBackupServiceActive(UserHandle.USER_SYSTEM, /* makeActive= */ false); + long ident = mInjector.binderClearCallingIdentity(); + try { + if (mInjector.getIBackupManager() != null) { + mInjector.getIBackupManager() + .setBackupServiceActive(UserHandle.USER_SYSTEM, false); + } + } catch (RemoteException e) { + throw new IllegalStateException("Failed deactivating backup service.", e); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + if (isAdb()) { // Log device owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER); @@ -7684,7 +7716,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveUserRestrictionsLocked(userId); } - long ident = mInjector.binderClearCallingIdentity(); + ident = mInjector.binderClearCallingIdentity(); try { // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); @@ -7941,9 +7973,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } - // Shutting down backup manager service permanently. - toggleBackupServiceActive(userHandle, /* makeActive= */ false); - mOwners.setProfileOwner(who, ownerName, userHandle); mOwners.writeProfileOwner(userHandle); Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle); @@ -7967,24 +7996,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - - private void toggleBackupServiceActive(int userId, boolean makeActive) { - // Shutting down backup manager service permanently. - enforceUserUnlocked(userId); - long ident = mInjector.binderClearCallingIdentity(); - try { - if (mInjector.getIBackupManager() != null) { - mInjector.getIBackupManager() - .setBackupServiceActive(userId, makeActive); - } - } catch (RemoteException e) { - throw new IllegalStateException("Failed deactivating backup service.", e); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - - } - @Override public void clearProfileOwner(ComponentName who) { if (!mHasFeature) { @@ -8800,7 +8811,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, PersistableBundle args, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(admin, "admin is null"); @@ -8817,7 +8828,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin, ComponentName agent, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return null; } Preconditions.checkNotNull(agent, "agent null"); @@ -12737,9 +12748,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(admin); - enforceProfileOrDeviceOwner(admin); - int userId = mInjector.userHandleGetCallingUserId(); - toggleBackupServiceActive(userId, enabled); + synchronized (getLockObject()) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + + final long ident = mInjector.binderClearCallingIdentity(); + try { + IBackupManager ibm = mInjector.getIBackupManager(); + if (ibm != null) { + ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, enabled); + } + } catch (RemoteException e) { + throw new IllegalStateException( + "Failed " + (enabled ? "" : "de") + "activating backup service.", e); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } } @Override @@ -12748,13 +12772,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return true; } - - enforceProfileOrDeviceOwner(admin); synchronized (getLockObject()) { try { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); IBackupManager ibm = mInjector.getIBackupManager(); - return ibm != null && ibm.isBackupServiceActive( - mInjector.userHandleGetCallingUserId()); + return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM); } catch (RemoteException e) { throw new IllegalStateException("Failed requesting backup service state.", e); } @@ -13215,7 +13237,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean setResetPasswordToken(ComponentName admin, byte[] token) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } if (token == null || token.length < 32) { @@ -13243,7 +13265,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean clearResetPasswordToken(ComponentName admin) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } synchronized (getLockObject()) { @@ -13269,6 +13291,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isResetPasswordTokenActive(ComponentName admin) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -13290,6 +13315,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token, int flags) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } Preconditions.checkNotNull(token); synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); @@ -13970,55 +13998,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void addCrossProfileCalendarPackage(ComponentName who, String packageName) { + public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) { if (!mHasFeature) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty"); synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (admin.mCrossProfileCalendarPackages.add(packageName)) { - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); - } + admin.mCrossProfileCalendarPackages = packageNames; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE) + .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES) .setAdmin(who) - .setStrings(packageName) + .setStrings(packageNames == null ? null + : packageNames.toArray(new String[packageNames.size()])) .write(); } @Override - public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) { - if (!mHasFeature) { - return false; - } - Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty"); - - boolean isRemoved = false; - synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName); - if (isRemoved) { - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); - } - } - if (isRemoved) { - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE) - .setAdmin(who) - .setStrings(packageName) - .write(); - } - return isRemoved; - } - - @Override public List<String> getCrossProfileCalendarPackages(ComponentName who) { if (!mHasFeature) { return Collections.emptyList(); @@ -14028,7 +14028,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - return new ArrayList<String>(admin.mCrossProfileCalendarPackages); + return admin.mCrossProfileCalendarPackages; } } @@ -14044,6 +14044,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); if (admin != null) { + if (admin.mCrossProfileCalendarPackages == null) { + return true; + } return admin.mCrossProfileCalendarPackages.contains(packageName); } } @@ -14059,7 +14062,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); if (admin != null) { - return new ArrayList<String>(admin.mCrossProfileCalendarPackages); + return admin.mCrossProfileCalendarPackages; } } return Collections.emptyList(); diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index 32513c1332bc..e99dd4f1cbae 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -21,9 +21,12 @@ import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteCursor; +import android.database.sqlite.SQLiteCursorDriver; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQuery; import android.net.NetworkUtils; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; @@ -35,6 +38,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; /** * Encapsulating class for using the SQLite database backing the memory store. @@ -46,6 +50,9 @@ import java.util.List; */ public class IpMemoryStoreDatabase { private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName(); + // A pair of NetworkAttributes objects is group-close if the confidence that they are + // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse. + private static final float GROUPCLOSE_CONFIDENCE = 0.5f; /** * Contract class for the Network Attributes table. @@ -187,30 +194,35 @@ public class IpMemoryStoreDatabase { return addresses; } + @NonNull + private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) { + final ContentValues values = new ContentValues(); + if (null == attributes) return values; + if (null != attributes.assignedV4Address) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, + NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + } + if (null != attributes.groupHint) { + values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); + } + if (null != attributes.dnsAddresses) { + values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, + encodeAddressList(attributes.dnsAddresses)); + } + if (null != attributes.mtu) { + values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); + } + return values; + } + // Convert a NetworkAttributes object to content values to store them in a table compliant // with the contract defined in NetworkAttributesContract. @NonNull private static ContentValues toContentValues(@NonNull final String key, @Nullable final NetworkAttributes attributes, final long expiry) { - final ContentValues values = new ContentValues(); + final ContentValues values = toContentValues(attributes); values.put(NetworkAttributesContract.COLNAME_L2KEY, key); values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry); - if (null != attributes) { - if (null != attributes.assignedV4Address) { - values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, - NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); - } - if (null != attributes.groupHint) { - values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); - } - if (null != attributes.dnsAddresses) { - values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, - encodeAddressList(attributes.dnsAddresses)); - } - if (null != attributes.mtu) { - values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); - } - } return values; } @@ -228,6 +240,32 @@ public class IpMemoryStoreDatabase { return values; } + @Nullable + private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) { + // Make sure the data hasn't expired + final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L); + if (expiry < System.currentTimeMillis()) return null; + + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final int assignedV4AddressInt = getInt(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); + final byte[] dnsAddressesBlob = + getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); + final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + if (0 != assignedV4AddressInt) { + builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + } + builder.setGroupHint(groupHint); + if (null != dnsAddressesBlob) { + builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); + } + if (mtu >= 0) { + builder.setMtu(mtu); + } + return builder.build(); + } + private static final String[] EXPIRY_COLUMN = new String[] { NetworkAttributesContract.COLNAME_EXPIRYDATE }; @@ -313,32 +351,9 @@ public class IpMemoryStoreDatabase { // result here. 0 results means the key was not found. if (cursor.getCount() != 1) return null; cursor.moveToFirst(); - - // Make sure the data hasn't expired - final long expiry = cursor.getLong( - cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE)); - if (expiry < System.currentTimeMillis()) return null; - - final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); - final int assignedV4AddressInt = getInt(cursor, - NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); - final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); - final byte[] dnsAddressesBlob = - getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); - final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + final NetworkAttributes attributes = readNetworkAttributesLine(cursor); cursor.close(); - - if (0 != assignedV4AddressInt) { - builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); - } - builder.setGroupHint(groupHint); - if (null != dnsAddressesBlob) { - builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); - } - if (mtu >= 0) { - builder.setMtu(mtu); - } - return builder.build(); + return attributes; } private static final String[] DATA_COLUMN = new String[] { @@ -365,17 +380,134 @@ public class IpMemoryStoreDatabase { return result; } + /** + * The following is a horrible hack that is necessary because the Android SQLite API does not + * have a way to query a binary blob. This, almost certainly, is an overlook. + * + * The Android SQLite API has two family of methods : one for query that returns data, and + * one for more general SQL statements that can execute any statement but may not return + * anything. All the query methods, however, take only String[] for the arguments. + * + * In principle it is simple to write a function that will encode the binary blob in the + * way SQLite expects it. However, because the API forces the argument to be coerced into a + * String, the SQLiteQuery object generated by the default query methods will bind all + * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types, + * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will + * be sanitized, changing the contents of the field, and the query will fail to match the + * blob. + * + * As far as I can tell, there are two possible ways around this problem. The first one + * is to put the data in the query string and eschew it being an argument. This would + * require doing the sanitizing by hand. The other is to call bindBlob directly on the + * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out + * sanitizing, but also will do the right thing if the underlying format ever changes. + * + * But none of the methods that take an SQLiteQuery object can return data ; this *must* + * be called with SQLiteDatabase#query. This object is not accessible from outside. + * However, there is a #query version that accepts a CursorFactory and this is pretty + * straightforward to implement as all the arguments are coming in and the SQLiteCursor + * class is public API. + * With this, it's possible to intercept the SQLiteQuery object, and assuming the args + * are available, to bind them directly and work around the API's oblivious coercion into + * Strings. + * + * This is really sad, but I don't see another way of having this work than this or the + * hand-rolled sanitizing, and this is the lesser evil. + */ + private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory { + @NonNull + private final ArrayList<Object> mArgs; + CustomCursorFactory(@NonNull final ArrayList<Object> args) { + mArgs = args; + } + @Override + public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery, + final String editTable, + final SQLiteQuery query) { + int index = 1; // bind is 1-indexed + for (final Object arg : mArgs) { + if (arg instanceof String) { + query.bindString(index++, (String) arg); + } else if (arg instanceof Long) { + query.bindLong(index++, (Long) arg); + } else if (arg instanceof Integer) { + query.bindLong(index++, Long.valueOf((Integer) arg)); + } else if (arg instanceof byte[]) { + query.bindBlob(index++, (byte[]) arg); + } else { + throw new IllegalStateException("Unsupported type CustomCursorFactory " + + arg.getClass().toString()); + } + } + return new SQLiteCursor(masterQuery, editTable, query); + } + } + + // Returns the l2key of the closest match, if and only if it matches + // closely enough (as determined by group-closeness). + @Nullable + static String findClosestAttributes(@NonNull final SQLiteDatabase db, + @NonNull final NetworkAttributes attr) { + if (attr.isEmpty()) return null; + final ContentValues values = toContentValues(attr); + + // Build the selection and args. To cut down on the number of lines to search, limit + // the search to those with at least one argument equals to the requested attributes. + // This works only because null attributes match only will not result in group-closeness. + final StringJoiner sj = new StringJoiner(" OR "); + final ArrayList<Object> args = new ArrayList<>(); + args.add(System.currentTimeMillis()); + for (final String field : values.keySet()) { + sj.add(field + " = ?"); + args.add(values.get(field)); + } + + final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND (" + + sj.toString() + ")"; + final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args), + false, // distinct + NetworkAttributesContract.TABLENAME, + null, // columns, null means everything + selection, // selection + null, // selectionArgs, horrendously passed to the cursor factory instead + null, // groupBy + null, // having + null, // orderBy + null); // limit + if (cursor.getCount() <= 0) return null; + cursor.moveToFirst(); + String bestKey = null; + float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this. + while (!cursor.isAfterLast()) { + final NetworkAttributes read = readNetworkAttributesLine(cursor); + final float confidence = read.getNetworkGroupSamenessConfidence(attr); + if (confidence > bestMatchConfidence) { + bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY); + bestMatchConfidence = confidence; + } + cursor.moveToNext(); + } + cursor.close(); + return bestKey; + } + // Helper methods - static String getString(final Cursor cursor, final String columnName) { + private static String getString(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getString(columnIndex) : null; } - static byte[] getBlob(final Cursor cursor, final String columnName) { + private static byte[] getBlob(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null; } - static int getInt(final Cursor cursor, final String columnName, final int defaultValue) { + private static int getInt(final Cursor cursor, final String columnName, + final int defaultValue) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue; } + private static long getLong(final Cursor cursor, final String columnName, + final long defaultValue) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue; + } } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java index 8b521f415925..d43dc6a24260 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -250,9 +250,26 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * Through the listener, returns the L2 key if one matched, or null. */ @Override - public void findL2Key(@NonNull final NetworkAttributesParcelable attributes, - @NonNull final IOnL2KeyResponseListener listener) { - // TODO : implement this. + public void findL2Key(@Nullable final NetworkAttributesParcelable attributes, + @Nullable final IOnL2KeyResponseListener listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == attributes) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + if (null == mDb) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb, + new NetworkAttributes(attributes)); + listener.onL2KeyResponse(makeStatus(SUCCESS), key); + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 98385c93aafe..586136802619 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; +import com.android.server.testharness.TestHarnessModeService; import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.textservices.TextServicesManagerService; import com.android.server.trust.TrustManagerService; @@ -872,7 +873,7 @@ public final class SystemServer { TimingsTraceLog traceLog = new TimingsTraceLog( SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD); - if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { + if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { Slog.e(TAG, "Unable to preload default resources"); } traceLog.traceEnd(); @@ -1157,6 +1158,10 @@ public final class SystemServer { traceBeginAndSlog("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); traceEnd(); + + traceBeginAndSlog("StartTestHarnessMode"); + mSystemServiceManager.startService(TestHarnessModeService.class); + traceEnd(); } if (hasPdb || OemLockService.isHalPresent()) { @@ -1556,6 +1561,12 @@ public final class SystemServer { traceEnd(); } + // Grants default permissions and defines roles + traceBeginAndSlog("StartRoleManagerService"); + mSystemServiceManager.startService(new RoleManagerService( + mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext))); + traceEnd(); + // We need to always start this service, regardless of whether the // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care // of initializing various settings. It will internally modify its behavior @@ -2006,12 +2017,6 @@ public final class SystemServer { } traceEnd(); - // Grants default permissions and defines roles - traceBeginAndSlog("StartRoleManagerService"); - mSystemServiceManager.startService(new RoleManagerService( - mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext))); - traceEnd(); - // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/net/Android.bp b/services/net/Android.bp index 3b4d6a75591f..30c7de57b73e 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -3,18 +3,18 @@ java_library_static { srcs: ["java/**/*.java"], } -// TODO: move to networking module with DhcpClient and remove lib -java_library { - name: "dhcp-packet-lib", - srcs: [ - "java/android/net/dhcp/*Packet.java", - ] -} - filegroup { name: "services-networkstack-shared-srcs", srcs: [ - "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient + "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering + "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer "java/android/net/shared/*.java", + ], +} + +java_library { + name: "services-netlink-lib", + srcs: [ + "java/android/net/netlink/*.java", ] } diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 0aec10149b23..2a2a67a92a86 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -16,8 +16,15 @@ package android.net.ip; +import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import android.content.Context; +import android.net.DhcpResultsParcelable; import android.net.LinkProperties; +import android.net.LinkPropertiesParcelable; +import android.net.NetworkStack; +import android.net.ip.IIpClientCallbacks; import android.os.ConditionVariable; import java.io.FileDescriptor; @@ -31,8 +38,8 @@ import java.io.PrintWriter; * @hide */ public class IpClientUtil { - // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg - public static final String DUMP_ARG = IpClient.DUMP_ARG; + // TODO: remove with its callers + public static final String DUMP_ARG = "ipclient"; /** * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is @@ -69,24 +76,129 @@ public class IpClientUtil { * * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of * {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} */ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) { - // TODO: request IpClient asynchronously from NetworkStack. - final IpClient ipClient = new IpClient(context, ifName, callback); - callback.onIpClientCreated(ipClient.makeConnector()); + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, new IpClientCallbacksProxy(callback)); + } + + /** + * Create a new IpClient. + * + * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy} + * instead of {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} + */ + public static void makeIpClient( + Context context, String ifName, IpClientCallbacksProxy callback) { + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, callback); + } + + /** + * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}. + */ + public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub { + protected final IpClientCallbacks mCb; + + /** + * Create a new IpClientCallbacksProxy. + */ + public IpClientCallbacksProxy(IpClientCallbacks cb) { + mCb = cb; + } + + @Override + public void onIpClientCreated(IIpClient ipClient) { + mCb.onIpClientCreated(ipClient); + } + + @Override + public void onPreDhcpAction() { + mCb.onPreDhcpAction(); + } + + @Override + public void onPostDhcpAction() { + mCb.onPostDhcpAction(); + } + + // This is purely advisory and not an indication of provisioning + // success or failure. This is only here for callers that want to + // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). + // DHCPv4 or static IPv4 configuration failure or success can be + // determined by whether or not the passed-in DhcpResults object is + // null or not. + @Override + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { + mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); + } + + @Override + public void onProvisioningSuccess(LinkPropertiesParcelable newLp) { + mCb.onProvisioningSuccess(fromStableParcelable(newLp)); + } + @Override + public void onProvisioningFailure(LinkPropertiesParcelable newLp) { + mCb.onProvisioningFailure(fromStableParcelable(newLp)); + } + + // Invoked on LinkProperties changes. + @Override + public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) { + mCb.onLinkPropertiesChange(fromStableParcelable(newLp)); + } + + // Called when the internal IpReachabilityMonitor (if enabled) has + // detected the loss of a critical number of required neighbors. + @Override + public void onReachabilityLost(String logMsg) { + mCb.onReachabilityLost(logMsg); + } + + // Called when the IpClient state machine terminates. + @Override + public void onQuit() { + mCb.onQuit(); + } + + // Install an APF program to filter incoming packets. + @Override + public void installPacketFilter(byte[] filter) { + mCb.installPacketFilter(filter); + } + + // Asynchronously read back the APF program & data buffer from the wifi driver. + // Due to Wifi HAL limitations, the current implementation only supports dumping the entire + // buffer. In response to this request, the driver returns the data buffer asynchronously + // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message. + @Override + public void startReadPacketFilter() { + mCb.startReadPacketFilter(); + } + + // If multicast filtering cannot be accomplished with APF, this function will be called to + // actuate multicast filtering using another means. + @Override + public void setFallbackMulticastFilter(boolean enabled) { + mCb.setFallbackMulticastFilter(enabled); + } + + // Enabled/disable Neighbor Discover offload functionality. This is + // called, for example, whenever 464xlat is being started or stopped. + @Override + public void setNeighborDiscoveryOffload(boolean enable) { + mCb.setNeighborDiscoveryOffload(enable); + } } /** * Dump logs for the specified IpClient. - * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys, - * then remove callers and delete. + * TODO: remove callers and delete */ public static void dumpIpClient( IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) { - if (!(connector instanceof IpClient.IpClientConnector)) { - pw.println("Invalid connector"); - return; - } - ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args); + pw.println("IpClient logs have moved to dumpsys network_stack"); } } diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 7910c9a69310..f7360f52225f 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -40,7 +40,7 @@ import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java index 80b2c2705038..be0f5f2d4f34 100644 --- a/services/net/java/android/net/util/NetdService.java +++ b/services/net/java/android/net/shared/NetdService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.util; +package android.net.shared; import android.net.INetd; import android.os.RemoteException; diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java index 463cf2af2897..3d2a2de45539 100644 --- a/services/net/java/android/net/shared/NetworkMonitorUtils.java +++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java @@ -16,6 +16,11 @@ package android.net.shared; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; + import android.content.Context; import android.net.NetworkCapabilities; import android.provider.Settings; @@ -58,9 +63,12 @@ public class NetworkMonitorUtils { * @param dfltNetCap Default requested network capabilities. * @param nc Network capabilities of the network to test. */ - public static boolean isValidationRequired( - NetworkCapabilities dfltNetCap, NetworkCapabilities nc) { + public static boolean isValidationRequired(NetworkCapabilities nc) { // TODO: Consider requiring validation for DUN networks. - return dfltNetCap.satisfiedByNetworkCapabilities(nc); + return nc != null + && nc.hasCapability(NET_CAPABILITY_INTERNET) + && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && nc.hasCapability(NET_CAPABILITY_TRUSTED) + && nc.hasCapability(NET_CAPABILITY_NOT_VPN); } } diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java new file mode 100644 index 000000000000..36945f5de2c5 --- /dev/null +++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.shared; + +import static android.Manifest.permission.NETWORK_STACK; + +import android.content.Context; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.INetworkManagementEventObserver; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.RouteInfo; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemClock; + +/** + * A class for reporting network events to clients. + * + * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to + * all INetworkManagementEventObserver objects that have registered with it. + * + * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService + * subclass) no longer call them directly. + * + * TODO: change from RemoteCallbackList to direct in-process callbacks. + */ +public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { + + private final Context mContext; + private final Handler mDaemonHandler; + private static final String TAG = "NetworkObserverRegistry"; + + /** + * Constructs a new instance and registers it with netd. + * This method should only be called once since netd will reject multiple registrations from + * the same process. + */ + public NetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + mContext = context; + mDaemonHandler = handler; + netd.registerUnsolicitedEventListener(this); + } + + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<>(); + + /** + * Registers the specified observer and start sending callbacks to it. + * This method may be called on any thread. + */ + public void registerObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.register(observer); + } + + /** + * Unregisters the specified observer and stop sending callbacks to it. + * This method may be called on any thread. + */ + public void unregisterObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.unregister(observer); + } + + @FunctionalInterface + private interface NetworkManagementEventCallback { + void sendCallback(INetworkManagementEventObserver o) throws RemoteException; + } + + private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { + final int length = mObservers.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + eventCallback.sendCallback(mObservers.getBroadcastItem(i)); + } catch (RemoteException | RuntimeException e) { + } + } + } finally { + mObservers.finishBroadcast(); + } + } + + /** + * Notify our observers of a change in the data activity state of the interface + */ + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( + Integer.toString(type), isActive, tsNanos)); + } + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive, + timestampNanos, uid, false)); + } + + /** + * Notify our observers of a limit reached. + */ + @Override + public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + /** + * Notify our observers of a limit reached. + */ + public void notifyLimitReached(String limitName, String iface) { + invokeForAllObservers(o -> o.limitReached(limitName, iface)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + /** + * Notify our observers of DNS server information received. + */ + public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { + invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + /** + * Notify our observers of a new or updated interface address. + */ + public void notifyAddressUpdated(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressUpdated(iface, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + /** + * Notify our observers of a deleted interface address. + */ + public void notifyAddressRemoved(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressRemoved(iface, address)); + } + + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + /** + * Notify our observers of an interface addition. + */ + public void notifyInterfaceAdded(String iface) { + invokeForAllObservers(o -> o.interfaceAdded(iface)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + /** + * Notify our observers of an interface removal. + */ + public void notifyInterfaceRemoved(String iface) { + invokeForAllObservers(o -> o.interfaceRemoved(iface)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + /** + * Notify our observers of an interface status change + */ + public void notifyInterfaceStatusChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + /** + * Notify our observers of an interface link state change + * (typically, an Ethernet cable has been plugged-in or unplugged). + */ + public void notifyInterfaceLinkStateChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + /** + * Notify our observers of a route change. + */ + public void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { + invokeForAllObservers(o -> o.routeUpdated(route)); + } else { + invokeForAllObservers(o -> o.routeRemoved(route)); + } + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't do anything here because this is not a method of INetworkManagementEventObserver. + // Only the NMS subclass will implement this. + } +} diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java index 7b060da01a89..f6bb87369cad 100644 --- a/services/net/java/android/net/util/InterfaceParams.java +++ b/services/net/java/android/net/util/InterfaceParams.java @@ -16,9 +16,6 @@ package android.net.util; -import static android.net.util.NetworkConstants.ETHER_MTU; -import static android.net.util.NetworkConstants.IPV6_MIN_MTU; - import static com.android.internal.util.Preconditions.checkArgument; import android.net.MacAddress; @@ -44,6 +41,11 @@ public class InterfaceParams { public final MacAddress macAddr; public final int defaultMtu; + // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack. + private static final int ETHER_MTU = 1500; + private static final int IPV6_MIN_MTU = 1280; + + public static InterfaceParams getByName(String name) { final NetworkInterface netif = getNetworkInterfaceByName(name); if (netif == null) return null; diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index c183b81362dc..ea5ce65f6f79 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -28,28 +28,6 @@ package android.net.util; public final class NetworkConstants { private NetworkConstants() { throw new RuntimeException("no instance permitted"); } - /** - * Ethernet constants. - * - * See also: - * - https://tools.ietf.org/html/rfc894 - * - https://tools.ietf.org/html/rfc2464 - * - https://tools.ietf.org/html/rfc7042 - * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml - * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml - */ - public static final int ETHER_DST_ADDR_OFFSET = 0; - public static final int ETHER_SRC_ADDR_OFFSET = 6; - public static final int ETHER_ADDR_LEN = 6; - - public static final int ETHER_TYPE_OFFSET = 12; - public static final int ETHER_TYPE_LENGTH = 2; - public static final int ETHER_TYPE_ARP = 0x0806; - public static final int ETHER_TYPE_IPV4 = 0x0800; - public static final int ETHER_TYPE_IPV6 = 0x86dd; - - public static final int ETHER_HEADER_LEN = 14; - public static final byte FF = asByte(0xff); public static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF @@ -58,34 +36,12 @@ public final class NetworkConstants { public static final int ETHER_MTU = 1500; /** - * ARP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc826 - * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml - */ - public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. - public static final int ARP_REQUEST = 1; - public static final int ARP_REPLY = 2; - public static final int ARP_HWTYPE_RESERVED_LO = 0; - public static final int ARP_HWTYPE_ETHER = 1; - public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; - - /** * IPv4 constants. * * See also: * - https://tools.ietf.org/html/rfc791 */ - public static final int IPV4_HEADER_MIN_LEN = 20; - public static final int IPV4_IHL_MASK = 0xf; - public static final int IPV4_FLAGS_OFFSET = 6; - public static final int IPV4_FRAGMENT_MASK = 0x1fff; - public static final int IPV4_PROTOCOL_OFFSET = 9; - public static final int IPV4_SRC_ADDR_OFFSET = 12; - public static final int IPV4_DST_ADDR_OFFSET = 16; public static final int IPV4_ADDR_BITS = 32; - public static final int IPV4_ADDR_LEN = 4; /** * IPv6 constants. @@ -93,15 +49,10 @@ public final class NetworkConstants { * See also: * - https://tools.ietf.org/html/rfc2460 */ - public static final int IPV6_HEADER_LEN = 40; - public static final int IPV6_PROTOCOL_OFFSET = 6; - public static final int IPV6_SRC_ADDR_OFFSET = 8; - public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_ADDR_BITS = 128; public static final int IPV6_ADDR_LEN = 16; public static final int IPV6_MIN_MTU = 1280; public static final int RFC7421_PREFIX_LENGTH = 64; - public static final int RFC6177_MIN_PREFIX_LENGTH = 48; /** * ICMP common (v4/v6) constants. @@ -124,45 +75,7 @@ public final class NetworkConstants { * - https://tools.ietf.org/html/rfc792 */ public static final int ICMPV4_ECHO_REQUEST_TYPE = 8; - - /** - * ICMPv6 constants. - * - * See also: - * - https://tools.ietf.org/html/rfc4443 - * - https://tools.ietf.org/html/rfc4861 - */ - public static final int ICMPV6_HEADER_MIN_LEN = 4; public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; - public static final int ICMPV6_ECHO_REPLY_TYPE = 129; - public static final int ICMPV6_ROUTER_SOLICITATION = 133; - public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; - public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; - public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; - - public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; - public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; - public static final int ICMPV6_ND_OPTION_SLLA = 1; - public static final int ICMPV6_ND_OPTION_TLLA = 2; - public static final int ICMPV6_ND_OPTION_MTU = 5; - - - /** - * UDP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc768 - */ - public static final int UDP_HEADER_LEN = 8; - - /** - * DHCP(v4) constants. - * - * See also: - * - https://tools.ietf.org/html/rfc2131 - */ - public static final int DHCP4_SERVER_PORT = 67; - public static final int DHCP4_CLIENT_PORT = 68; /** * DNS constants. @@ -176,9 +89,4 @@ public final class NetworkConstants { * Utility functions. */ public static byte asByte(int i) { return (byte) i; } - - public static String asString(int i) { return Integer.toString(i); } - - public static int asUint(byte b) { return (b & 0xff); } - public static int asUint(short s) { return (s & 0xffff); } } diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 4811523b22f9..164570a84cb0 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -116,6 +116,7 @@ import com.android.server.backup.testing.TransportTestUtils; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.testing.shadows.FrameworkShadowLooper; import com.android.server.testing.shadows.ShadowApplicationPackageManager; +import com.android.server.testing.shadows.ShadowBackupActivityThread; import com.android.server.testing.shadows.ShadowBackupDataInput; import com.android.server.testing.shadows.ShadowBackupDataOutput; import com.android.server.testing.shadows.ShadowEventLog; @@ -163,6 +164,7 @@ import java.util.stream.Stream; ShadowBackupDataOutput.class, ShadowEventLog.class, ShadowQueuedWork.class, + ShadowBackupActivityThread.class, }) @Presubmit public class KeyValueBackupTaskTest { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java index aefc871d2639..33b8aa73d293 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java @@ -62,7 +62,7 @@ public class ShadowAppBackupUtils { } @Implementation - protected static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) { + protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) { return sAppsEligibleForBackup.contains(app.packageName); } diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java new file mode 100644 index 000000000000..ca2e3b6dafef --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.testing.shadows; + +import android.app.ActivityThread; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; + +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowActivityThread; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.annotation.Nonnull; + +/** + * Extends the existing {@link ShadowActivityThread} to add support for + * {@link PackageManager#getApplicationEnabledSetting(String)} in the shadow {@link PackageManager} + * returned by {@link ShadowBackupActivityThread#getPackageManager()}. + */ +@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true) +public class ShadowBackupActivityThread extends ShadowActivityThread { + @Implementation + public static Object getPackageManager() { + ClassLoader classLoader = ShadowActivityThread.class.getClassLoader(); + Class<?> iPackageManagerClass; + try { + iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return Proxy.newProxyInstance( + classLoader, + new Class[] {iPackageManagerClass}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, @Nonnull Method method, Object[] args) + throws Exception { + if (method.getName().equals("getApplicationInfo")) { + String packageName = (String) args[0]; + int flags = (Integer) args[1]; + + try { + return RuntimeEnvironment.application + .getPackageManager() + .getApplicationInfo(packageName, flags); + } catch (PackageManager.NameNotFoundException e) { + throw new RemoteException(e.getMessage()); + } + } else if (method.getName().equals("getApplicationEnabledSetting")) { + return 0; + } else { + return null; + } + } + }); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 6a153d5346ed..6386b3b396ae 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -28,14 +28,18 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.AlarmManagerService.ACTIVE_INDEX; +import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; +import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED; import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME; import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME; -import static com.android.server.AlarmManagerService.Constants - .KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; +import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; +import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED; import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL; +import static com.android.server.AlarmManagerService.WORKING_INDEX; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -58,6 +62,7 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -65,7 +70,6 @@ import android.provider.Settings; import android.util.Log; import android.util.SparseArray; -import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.annotations.GuardedBy; @@ -83,7 +87,6 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; @Presubmit -@SmallTest @RunWith(AndroidJUnit4.class) public class AlarmManagerServiceTest { private static final String TAG = AlarmManagerServiceTest.class.getSimpleName(); @@ -91,7 +94,9 @@ public class AlarmManagerServiceTest { private static final int SYSTEM_UI_UID = 123456789; private static final int TEST_CALLING_UID = 12345; + private long mAppStandbyWindow; private AlarmManagerService mService; + private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener; @Mock private ContentResolver mMockResolver; @Mock @@ -229,16 +234,23 @@ public class AlarmManagerServiceTest { mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); doNothing().when(mService).publishBinderService(any(), any()); + mService.onStart(); - mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); spyOn(mService.mHandler); - - assertEquals(0, mService.mConstants.MIN_FUTURITY); assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID); assertEquals(mService.mClockReceiver, mClockReceiver); assertEquals(mService.mWakeLock, mWakeLock); verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(), isNull()); + + // Other boot phases don't matter + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + assertEquals(0, mService.mConstants.MIN_FUTURITY); + mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; + ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor = + ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class); + verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture()); + mAppStandbyListener = captor.getValue(); } private void setTestAlarm(int type, long triggerTime, PendingIntent operation) { @@ -254,6 +266,28 @@ public class AlarmManagerServiceTest { return mockPi; } + /** + * Careful while calling as this will replace any existing settings for the calling test. + */ + private void setQuotasEnabled(boolean enabled) { + final StringBuilder constantsBuilder = new StringBuilder(); + constantsBuilder.append(KEY_MIN_FUTURITY); + constantsBuilder.append("=0,"); + // Capping active and working quotas to make testing feasible. + constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]); + constantsBuilder.append("=8,"); + constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]); + constantsBuilder.append("=5,"); + if (!enabled) { + constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED); + constantsBuilder.append("=false,"); + } + doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver, + Settings.Global.ALARM_MANAGER_CONSTANTS)); + mService.mConstants.onChange(false, null); + assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled); + } + @Test public void testSingleAlarmSet() { final long triggerTime = mNowElapsedTest + 5000; @@ -346,6 +380,7 @@ public class AlarmManagerServiceTest { @Test public void testStandbyBucketDelay_workingSet() throws Exception { + setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); @@ -366,6 +401,7 @@ public class AlarmManagerServiceTest { @Test public void testStandbyBucketDelay_frequent() throws Exception { + setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); @@ -385,6 +421,7 @@ public class AlarmManagerServiceTest { @Test public void testStandbyBucketDelay_rare() throws Exception { + setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); @@ -402,6 +439,253 @@ public class AlarmManagerServiceTest { assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed()); } + private void testQuotasDeferralOnSet(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, + getNewMockPendingIntent()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + // This one should get deferred on set + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, + getNewMockPendingIntent()); + final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; + assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); + } + + private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, + getNewMockPendingIntent()); + } + // This one should get deferred after the latest alarm expires + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, + getNewMockPendingIntent()); + for (int i = 0; i < quota; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; + assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); + } + + private void testQuotasNoDeferral(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, + getNewMockPendingIntent()); + } + // This delivery time maintains the quota invariant. Should not be deferred. + final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5; + setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent()); + for (int i = 0; i < quota; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); + } + + @Test + public void testActiveQuota_deferredOnSet() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); + } + + @Test + public void testActiveQuota_deferredOnExpiration() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); + } + + @Test + public void testActiveQuota_notDeferred() throws Exception { + setQuotasEnabled(true); + testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); + } + + @Test + public void testWorkingQuota_deferredOnSet() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); + } + + @Test + public void testWorkingQuota_deferredOnExpiration() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); + } + + @Test + public void testWorkingQuota_notDeferred() throws Exception { + setQuotasEnabled(true); + testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); + } + + @Test + public void testFrequentQuota_deferredOnSet() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); + } + + @Test + public void testFrequentQuota_deferredOnExpiration() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); + } + + @Test + public void testFrequentQuota_notDeferred() throws Exception { + setQuotasEnabled(true); + testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); + } + + @Test + public void testRareQuota_deferredOnSet() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnSet(STANDBY_BUCKET_RARE); + } + + @Test + public void testRareQuota_deferredOnExpiration() throws Exception { + setQuotasEnabled(true); + testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); + } + + @Test + public void testRareQuota_notDeferred() throws Exception { + setQuotasEnabled(true); + testQuotasNoDeferral(STANDBY_BUCKET_RARE); + } + + private void sendAndHandleBucketChanged(int bucket) { + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(bucket); + // Stubbing the handler call to simulate it synchronously here. + doReturn(true).when(mService.mHandler).sendMessage(any(Message.class)); + mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE, + UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0); + final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture()); + final Message lastMessage = messageCaptor.getValue(); + assertEquals("Unexpected message send to handler", lastMessage.what, + APP_STANDBY_BUCKET_CHANGED); + mService.mHandler.handleMessage(lastMessage); + } + + @Test + public void testQuotaDowngrade() throws Exception { + setQuotasEnabled(true); + final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); + + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < workingQuota; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); + } + // No deferrals now. + for (int i = 0; i < workingQuota - 1; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + assertEquals(firstTrigger + i, mNowElapsedTest); + mTestTimer.expire(); + } + // The next upcoming alarm in queue should also be set as expected. + assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed()); + // Downgrading the bucket now + sendAndHandleBucketChanged(STANDBY_BUCKET_RARE); + final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE); + // The last alarm should now be deferred. + final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota) + + mAppStandbyWindow + 1; + assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); + } + + @Test + public void testQuotaUpgrade() throws Exception { + setQuotasEnabled(true); + final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT); + + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < frequentQuota + 1; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); + if (i < frequentQuota) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + } + // The last alarm should be deferred due to exceeding the quota + final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow; + assertEquals(deferredTrigger, mTestTimer.getElapsed()); + + // Upgrading the bucket now + sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE); + // The last alarm should now be rescheduled to go as per original expectations + final long originalTrigger = firstTrigger + frequentQuota; + assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed()); + } + + private void sendAndHandleParoleChanged(boolean parole) { + // Stubbing the handler call to simulate it synchronously here. + doReturn(true).when(mService.mHandler).sendMessage(any(Message.class)); + mAppStandbyListener.onParoleStateChanged(parole); + final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture()); + final Message lastMessage = messageCaptor.getValue(); + assertEquals("Unexpected message send to handler", lastMessage.what, + APP_STANDBY_PAROLE_CHANGED); + mService.mHandler.handleMessage(lastMessage); + } + + @Test + public void testParole() throws Exception { + setQuotasEnabled(true); + final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); + + final long firstTrigger = mNowElapsedTest + 10; + final int totalAlarms = workingQuota + 10; + for (int i = 0; i < totalAlarms; i++) { + setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); + } + // Use up the quota, no deferrals expected. + for (int i = 0; i < workingQuota; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + assertEquals(firstTrigger + i, mNowElapsedTest); + mTestTimer.expire(); + } + // Any subsequent alarms in queue should all be deferred + assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed()); + // Paroling now + sendAndHandleParoleChanged(true); + + // Subsequent alarms should now go off as per original expectations. + for (int i = 0; i < 5; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest); + mTestTimer.expire(); + } + // Come out of parole + sendAndHandleParoleChanged(false); + + // Subsequent alarms should again get deferred + final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow; + assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); + } + @Test public void testAlarmRestrictedInBatterSaver() throws Exception { final ArgumentCaptor<AppStateTracker.Listener> listenerArgumentCaptor = diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 1a16e568ca53..53d72bb9a415 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -1376,6 +1376,45 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_ACTIVE); } + @Test + public void testStepToIdleMode() { + float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE; + for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL; + mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG; + mode++) { + int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode); + if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop + //workable in this test class + mDeviceIdleController.updatePreIdleFactor(); + float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode); + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode [" + mode + "].", + expectedfactor, curfactor, delta); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE); + checkMaybeDoAnImmediateMaintenance(expectedfactor); + } + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode default.", + 1.0f, curfactor, delta); + } + private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: @@ -1599,4 +1638,84 @@ public class DeviceIdleControllerTest { fail("Conditions for " + lightStateToString(expectedLightState) + " unknown."); } } + + private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) { + final long errorTolerance = 1000; + enterDeepState(state); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + long newDelay = (long) ((alarm - now) * factor); + assertTrue("setPreIdleTimeoutFactor: " + factor, + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("resetPreIdleTimeoutMode from: " + factor, + Math.abs(newAlarm - alarm) < errorTolerance); + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + now = SystemClock.elapsedRealtime(); + enterDeepState(state); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle", + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } else { + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + + " shounld not change next alarm" , + (newAlarm == alarm)); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } + + private void checkMaybeDoAnImmediateMaintenance(float factor) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + final long minuteInMillis = 60 * 1000; + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + enterDeepState(STATE_IDLE); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 0.6)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6", + newAlarm == alarm); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 1.2)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2", + (newAlarm - now) < minuteInMillis); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 57ee6dcad9f2..cad71a26a76b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; @@ -370,16 +371,19 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, "com.android.test.stay", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); + final int uid = 10001; + mQuotaController.onAppRemovedLocked("com.android.test.remove", uid); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); assertEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX)); assertNotEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX)); + + assertFalse(mQuotaController.getForegroundUids().get(uid)); } @Test @@ -405,7 +409,7 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(10, "com.android.test", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; mQuotaController.onUserRemovedLocked(0); @@ -440,14 +444,14 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; // Invalid time is now +24 hours since there are no sessions at all for the app. - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; // Invalid time is now +18 hours since there are no sessions in the window but the earliest // session is 6 hours ago. - expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 0; expectedStats.bgJobCountInWindow = 0; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -457,7 +461,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -468,7 +472,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -479,7 +483,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; // Invalid time is now +44 minutes since the earliest session in the window is now-5 // minutes. - expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -489,7 +493,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; // Invalid time is now since the session is at the very edge of the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 4; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -500,7 +504,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -510,7 +514,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -523,7 +527,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; // Invalid time is now +59 minutes since the earliest session in the window is now-121 // minutes. - expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -536,7 +540,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -546,14 +550,14 @@ public class QuotaControllerTest { mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); assertEquals(expectedStats, inputStats); - // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period. + // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period. mQuotaController.getTimingSessions(0, "com.android.test") .add(0, createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now +1 hour since the earliest session in the max period is 1 hour // before the end of the max period cutoff time. - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; @@ -569,7 +573,7 @@ public class QuotaControllerTest { 2 * MINUTE_IN_MILLIS, 2)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now since the earlist session straddles the max period cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; @@ -599,7 +603,7 @@ public class QuotaControllerTest { // Active expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -609,7 +613,7 @@ public class QuotaControllerTest { // Working expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -621,7 +625,7 @@ public class QuotaControllerTest { // Frequent expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -633,7 +637,7 @@ public class QuotaControllerTest { // Rare expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 20; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -675,7 +679,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; @@ -688,7 +692,7 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsActive); expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed; @@ -698,7 +702,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsWorking); expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed; @@ -708,7 +712,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsFrequent); expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed; @@ -719,6 +723,77 @@ public class QuotaControllerTest { } @Test + public void testIsWithinQuotaLocked_NeverApp() { + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_Charging() { + setCharging(); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam", + WORKING_INDEX)); + + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000)); + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500)); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent", + FREQUENT_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test public void testMaybeScheduleCleanupAlarmLocked() { // No sessions saved yet. mQuotaController.maybeScheduleCleanupAlarmLocked(); @@ -752,6 +827,7 @@ public class QuotaControllerTest { // Active window size is 10 minutes. final int standbyBucket = ACTIVE_INDEX; + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); // No sessions saved yet. mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1016,11 +1092,37 @@ public class QuotaControllerTest { .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); - inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), - any()); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class)); } + @Test + public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int standbyBucket = WORKING_INDEX; + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + stats.jobCountInAllowedTime = + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2; + + // Invalid time in the past, so the count shouldn't be used. + stats.jobCountExpirationTimeElapsed = + now - mQuotaController.getAllowedTimePerPeriodMs() / 2; + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Invalid time in the future, so the count should be used. + stats.jobCountExpirationTimeElapsed = + now + mQuotaController.getAllowedTimePerPeriodMs() / 2; + final long expectedWorkingAlarmTime = + stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs(); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } /** * Tests that the start alarm is properly rescheduled if the earliest session that contributes @@ -1172,6 +1274,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500; mQuotaController.onConstantsUpdatedLocked(); @@ -1183,11 +1290,16 @@ public class QuotaControllerTest { mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); } @Test public void testConstantsUpdating_InvalidValues() { - // Test negatives + // Test negatives/too low. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS; @@ -1195,6 +1307,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0; mQuotaController.onConstantsUpdatedLocked(); @@ -1205,6 +1322,11 @@ public class QuotaControllerTest { assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); // Test larger than a day. Controller should cap at one day. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS; @@ -1246,6 +1368,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_Discharging() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_BACKUP); JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1293,6 +1416,8 @@ public class QuotaControllerTest { */ @Test public void testTimerTracking_ChargingAndDischarging() { + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2); @@ -1363,6 +1488,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_AllBackground() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1503,6 +1629,64 @@ public class QuotaControllerTest { } /** + * Tests that Timers don't track job counts while in the foreground. + */ + @Test + public void testTimerTracking_JobCount_Foreground() { + setDischarging(); + + final int standbyBucket = ACTIVE_INDEX; + JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1); + JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2); + + mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobFg2, null); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + mQuotaController.prepareForExecutionLocked(jobFg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobFg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + assertEquals(0, stats.jobCountInAllowedTime); + } + + /** + * Tests that Timers properly track job counts while in the background. + */ + @Test + public void testTimerTracking_JobCount_Background() { + final int standbyBucket = WORKING_INDEX; + JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1); + JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2); + mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); + mQuotaController.prepareForExecutionLocked(jobBg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobBg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + + assertEquals(2, stats.jobCountInAllowedTime); + } + + /** * Tests that Timers properly track overlapping top and background jobs. */ @Test @@ -1680,6 +1864,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_HOME); // Now the package only has two seconds to run. final long remainingTimeMs = 2 * SECOND_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1707,6 +1892,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); Handler handler = mQuotaController.getHandler(); spyOn(handler); diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java index f31ca55d422e..5009d64b94d4 100644 --- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java @@ -19,51 +19,41 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.NotificationManager; import android.content.ContentResolver; +import android.content.Context; import android.content.res.Resources; import android.provider.Settings.Global; -import android.test.mock.MockContext; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.google.common.base.Objects; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.HashMap; +import java.util.Objects; /** - atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java + * atest com.android.server.power.batterysaver.BatterySaverStateMachineTest */ @SmallTest @RunWith(AndroidJUnit4.class) public class BatterySaverStateMachineTest { - private MyMockContext mMockContext; + private Context mMockContext; private ContentResolver mMockContextResolver; private BatterySaverController mMockBatterySaverController; + private NotificationManager mMockNotificationManager; private Device mDevice; private TestableBatterySaverStateMachine mTarget; private Resources mMockResources; - private class MyMockContext extends MockContext { - @Override - public ContentResolver getContentResolver() { - return mMockContextResolver; - } - - @Override - public Resources getResources() { - return mMockResources; - } - } - private DevicePersistedState mPersistedState; private class DevicePersistedState { @@ -116,6 +106,10 @@ public class BatterySaverStateMachineTest { mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0, mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0, mDevice.getLowPowerModeTriggerLevel(), + mPersistedState.global.getOrDefault( + Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0, + mPersistedState.global.getOrDefault( + Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90), mPersistedState.global.getOrDefault(Global.AUTOMATIC_POWER_SAVER_MODE, 0), mPersistedState.global.getOrDefault( Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0, @@ -137,13 +131,13 @@ public class BatterySaverStateMachineTest { * Test target class. */ private class TestableBatterySaverStateMachine extends BatterySaverStateMachine { - public TestableBatterySaverStateMachine() { + TestableBatterySaverStateMachine() { super(new Object(), mMockContext, mMockBatterySaverController); } @Override protected void putGlobalSetting(String key, int value) { - if (Objects.equal(mPersistedState.global.get(key), value)) { + if (Objects.equals(mPersistedState.global.get(key), value)) { return; } mDevice.putGlobalSetting(key, value); @@ -163,15 +157,25 @@ public class BatterySaverStateMachineTest { void runOnBgThreadLazy(Runnable r, int delayMillis) { r.run(); } + + @Override + void triggerDynamicModeNotification() { + // Do nothing + } } @Before public void setUp() { - mMockContext = new MyMockContext(); + mMockContext = mock(Context.class); mMockContextResolver = mock(ContentResolver.class); mMockBatterySaverController = mock(BatterySaverController.class); + mMockNotificationManager = mock(NotificationManager.class); mMockResources = mock(Resources.class); + doReturn(mMockContextResolver).when(mMockContext).getContentResolver(); + doReturn(mMockResources).when(mMockContext).getResources(); + doReturn(mMockNotificationManager).when(mMockContext) + .getSystemService(NotificationManager.class); doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0)) .when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt()); when(mMockBatterySaverController.isEnabled()) @@ -446,8 +450,9 @@ public class BatterySaverStateMachineTest { } @Test - public void testAutoBatterySaver_withSticky() { + public void testAutoBatterySaver_withSticky_withAutoOffDisabled() { mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0); mTarget.setBatterySaverEnabledManually(true); @@ -518,6 +523,197 @@ public class BatterySaverStateMachineTest { } @Test + public void testAutoBatterySaver_withSticky_withAutoOffEnabled() { + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1); + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90); + + // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even + // with battery level change above threshold. + mDevice.setBatteryLevel(100); + mTarget.setBatterySaverEnabledManually(true); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(100, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setBatteryLevel(95); + + assertEquals(true, mDevice.batterySaverEnabled); // Stays on. + assertEquals(95, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 2: User turns BS on manually above the threshold then charges device. BS + // shouldn't turn back on. + mDevice.setPowered(true); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(95, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setBatteryLevel(97); + mDevice.setPowered(false); + + assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled. + assertEquals(97, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 3: User turns BS on manually above the threshold. Device drains below + // threshold and then charged to below threshold. Sticky BS should activate. + mTarget.setBatterySaverEnabledManually(true); + mDevice.setBatteryLevel(30); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(30, mPersistedState.batteryLevel); + assertEquals(true, mPersistedState.batteryLow); + + mDevice.setPowered(true); + mDevice.setBatteryLevel(80); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(80, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setPowered(false); + + assertEquals(true, mDevice.batterySaverEnabled); // Still enabled. + assertEquals(80, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setBatteryLevel(30); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(30, mPersistedState.batteryLevel); + assertEquals(true, mPersistedState.batteryLow); + + // Scenario 4: User turns BS on manually above the threshold. Device drains below + // threshold and is eventually charged to above threshold. Sticky BS should turn off. + mDevice.setPowered(true); + mDevice.setBatteryLevel(90); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(90, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setPowered(false); + + assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled. + assertEquals(90, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 5: User turns BS on manually below threshold and charges to below threshold. + // Sticky BS should activate. + mDevice.setBatteryLevel(70); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(70, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mTarget.setBatterySaverEnabledManually(true); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(70, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setPowered(true); + mDevice.setBatteryLevel(80); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(80, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setPowered(false); + + assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on. + assertEquals(80, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 6: User turns BS on manually below threshold and eventually charges to above + // threshold. Sticky BS should turn off. + + mDevice.setPowered(true); + mDevice.setBatteryLevel(95); + mDevice.setPowered(false); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(95, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS + // shouldn't activate. + mTarget.setBatterySaverEnabledManually(true); + mPersistedState.batteryLevel = 93; + + initDevice(); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(93, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 8: User turns BS on below threshold and then reboots device without charging. + // Sticky BS should activate. + mDevice.setBatteryLevel(75); + mTarget.setBatterySaverEnabledManually(true); + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(75, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + initDevice(); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(75, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 9: User turns BS on below threshold and then reboots device after charging + // above threshold. Sticky BS shouldn't activate. + mDevice.setBatteryLevel(80); + mTarget.setBatterySaverEnabledManually(true); + mPersistedState.batteryLevel = 100; + + initDevice(); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(100, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + // Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel + // and then user enables manually above both thresholds, discharges below + // autoDisableLevel and then charges up to between autoDisableLevel and + // lowPowerModeTriggerLevel. Sticky BS shouldn't activate, but BS should still be on + // because the level is below lowPowerModeTriggerLevel. + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 75); + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1); + mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 60); + initDevice(); + + mDevice.setBatteryLevel(90); + mTarget.setBatterySaverEnabledManually(true); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(90, mPersistedState.batteryLevel); + assertEquals(false, mPersistedState.batteryLow); + + mDevice.setBatteryLevel(50); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(50, mPersistedState.batteryLevel); + assertEquals(true, mPersistedState.batteryLow); + + mDevice.setPowered(true); + mDevice.setBatteryLevel(65); + + assertEquals(false, mDevice.batterySaverEnabled); + assertEquals(65, mPersistedState.batteryLevel); + assertEquals(true, mPersistedState.batteryLow); + + mDevice.setPowered(false); + + assertEquals(true, mDevice.batterySaverEnabled); + assertEquals(65, mPersistedState.batteryLevel); + assertEquals(true, mPersistedState.batteryLow); + } + + @Test public void testAutoBatterySaver_withStickyDisabled() { when(mMockResources.getBoolean( com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled)) diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 1b5ba263e6dd..dc31c0f7ebbb 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -156,6 +156,7 @@ </activity> <activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" /> + <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" /> <activity-alias android:name="a.ShortcutEnabled" android:targetActivity="com.android.server.pm.ShortcutTestActivity" diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java new file mode 100644 index 000000000000..782dc3e2ad45 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2018 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.accessibility; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER; + +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; +import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.display.DisplayManagerGlobal; +import android.os.Looper; +import android.os.SystemClock; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for AccessibilityInputFilterTest + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityInputFilterTest { + private static int sNextDisplayId = DEFAULT_DISPLAY; + private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 1; + private static final float DEFAULT_X = 100f; + private static final float DEFAULT_Y = 100f; + + private final SparseArray<EventStreamTransformation> mEventHandler = new SparseArray<>(0); + private final ArrayList<Display> mDisplayList = new ArrayList<>(); + private final int mFeatures = FLAG_FEATURE_AUTOCLICK + | FLAG_FEATURE_TOUCH_EXPLORATION + | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER + | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER + | FLAG_FEATURE_INJECT_MOTION_EVENTS + | FLAG_FEATURE_FILTER_KEY_EVENTS; + + // The expected order of EventStreamTransformations. + private final Class[] mExpectedEventHandlerTypes = + {KeyboardInterceptor.class, MotionEventInjector.class, + MagnificationGestureHandler.class, TouchExplorer.class, + AutoclickController.class, AccessibilityInputFilter.class}; + + private MagnificationController mMockMagnificationController; + private AccessibilityManagerService mAms; + private AccessibilityInputFilter mA11yInputFilter; + private EventCaptor mCaptor1; + private EventCaptor mCaptor2; + private long mLastDownTime = Integer.MIN_VALUE; + + private class EventCaptor implements EventStreamTransformation { + List<InputEvent> mEvents = new ArrayList<>(); + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mEvents.add(event.copy()); + } + + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + mEvents.add(event.copy()); + } + + @Override + public void setNext(EventStreamTransformation next) { + } + + @Override + public EventStreamTransformation getNext() { + return null; + } + + @Override + public void clearEvents(int inputSource) { + clear(); + } + + private void clear() { + mEvents.clear(); + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getContext(); + + setDisplayCount(1); + mAms = spy(new AccessibilityManagerService(context)); + mMockMagnificationController = mock(MagnificationController.class); + mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler); + mA11yInputFilter.onInstalled(); + + when(mAms.getValidDisplayList()).thenReturn(mDisplayList); + when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController); + } + + @After + public void tearDown() { + mA11yInputFilter.onUninstalled(); + } + + @Test + public void testEventHandler_shouldChangeAfterSetUserAndEnabledFeatures() { + prepareLooper(); + + // Check if there is no mEventHandler when no feature is set. + assertEquals(0, mEventHandler.size()); + + // Check if mEventHandler is added/removed after setting a11y features. + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(1, mEventHandler.size()); + + mA11yInputFilter.setUserAndEnabledFeatures(0, 0); + assertEquals(0, mEventHandler.size()); + } + + @Test + public void testEventHandler_shouldChangeAfterOnDisplayChanged() { + prepareLooper(); + + // Check if there is only one mEventHandler when there is one default display. + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(1, mEventHandler.size()); + + // Check if it has correct numbers of mEventHandler for corresponding displays. + setDisplayCount(4); + mA11yInputFilter.onDisplayChanged(); + assertEquals(4, mEventHandler.size()); + + setDisplayCount(2); + mA11yInputFilter.onDisplayChanged(); + assertEquals(2, mEventHandler.size()); + } + + @Test + public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(2, mEventHandler.size()); + + // Check if mEventHandler for each display has correct order of the + // EventStreamTransformations. + EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY); + for (int i = 0; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + next = next.getNext(); + } + + next = mEventHandler.get(SECOND_DISPLAY); + // Start from index 1 because KeyboardInterceptor only exists in EventHandler for + // DEFAULT_DISPLAY. + for (int i = 1; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + next = next.getNext(); + } + } + + @Test + public void testInputEvent_shouldDispatchToCorrespondingEventHandlers() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(2, mEventHandler.size()); + + mCaptor1 = new EventCaptor(); + mCaptor2 = new EventCaptor(); + mEventHandler.put(DEFAULT_DISPLAY, mCaptor1); + mEventHandler.put(SECOND_DISPLAY, mCaptor2); + + // InputEvent with different displayId should be dispatched to corresponding EventHandler. + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + + assertEquals(1, mCaptor1.mEvents.size()); + assertEquals(1, mCaptor2.mEvents.size()); + } + + @Test + public void testInputEvent_shouldClearEventsForAllEventHandlers() { + prepareLooper(); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(1, mEventHandler.size()); + + mCaptor1 = new EventCaptor(); + mEventHandler.put(DEFAULT_DISPLAY, mCaptor1); + + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN)); + assertEquals(2, mCaptor1.mEvents.size()); + + // InputEvent with different input source should trigger clearEvents() for each + // EventStreamTransformation in EventHandler. + send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE)); + assertEquals(1, mCaptor1.mEvents.size()); + } + + private static void prepareLooper() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + } + + private Display createStubDisplay(DisplayInfo displayInfo) { + final int displayId = sNextDisplayId++; + final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, + displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); + return display; + } + + private void setDisplayCount(int count) { + sNextDisplayId = DEFAULT_DISPLAY; + mDisplayList.clear(); + for (int i = 0; i < count; i++) { + mDisplayList.add(createStubDisplay(new DisplayInfo())); + } + } + + private void send(InputEvent event) { + mA11yInputFilter.onInputEvent(event, /* policyFlags */ FLAG_PASS_TO_USER); + } + + private MotionEvent downEvent(int displayId, int source) { + mLastDownTime = SystemClock.uptimeMillis(); + final MotionEvent ev = MotionEvent.obtain(mLastDownTime, mLastDownTime, + MotionEvent.ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0); + ev.setDisplayId(displayId); + ev.setSource(source); + return ev; + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index d91ce39ea92c..de7d77d4b963 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -158,7 +158,7 @@ public class MagnificationGestureHandlerTest { boolean detectShortcutTrigger) { MagnificationGestureHandler h = new MagnificationGestureHandler( mContext, mMagnificationController, - detectTripleTap, detectShortcutTrigger); + detectTripleTap, detectShortcutTrigger, DISPLAY_0); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override protected String messageToString(Message m) { diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java new file mode 100644 index 000000000000..65af677182b4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2018 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.adb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.FgThread; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(JUnit4.class) +public final class AdbDebuggingManagerTest { + + private static final String TAG = "AdbDebuggingManagerTest"; + + // This component is passed to the AdbDebuggingManager to act as the activity that can confirm + // unknown adb keys. An overlay package was first attempted to override the + // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was + // not being read. + private static final String ADB_CONFIRM_COMPONENT = + "com.android.frameworks.servicestests/" + + "com.android.server.adb.AdbDebuggingManagerTestActivity"; + + // The base64 encoding of the values 'test key 1' and 'test key 2'. + private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo="; + private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo="; + + // This response is received from the AdbDebuggingHandler when the key is allowed to connect + private static final String RESPONSE_KEY_ALLOWED = "OK"; + // This response is received from the AdbDebuggingHandler when the key is not allowed to connect + private static final String RESPONSE_KEY_DENIED = "NO"; + + // wait up to 5 seconds for any blocking queries + private static final long TIMEOUT = 5000; + private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS; + + private Context mContext; + private AdbDebuggingManager mManager; + private AdbDebuggingManager.AdbDebuggingThread mThread; + private AdbDebuggingManager.AdbDebuggingHandler mHandler; + private AdbDebuggingManager.AdbKeyStore mKeyStore; + private BlockingQueue<TestResult> mBlockingQueue; + private long mOriginalAllowedConnectionTime; + private File mKeyFile; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT); + mKeyFile = new File(mContext.getFilesDir(), "test_adb_keys.xml"); + if (mKeyFile.exists()) { + mKeyFile.delete(); + } + mThread = new AdbDebuggingThreadTest(); + mKeyStore = mManager.new AdbKeyStore(mKeyFile); + mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore); + mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime(); + mBlockingQueue = new ArrayBlockingQueue<>(1); + + } + + @After + public void tearDown() throws Exception { + mKeyStore.deleteKeyStore(); + setAllowedConnectionTime(mOriginalAllowedConnectionTime); + } + + /** + * Sets the allowed connection time within which a subsequent connection from a key for which + * the user selected the 'Always allow' option will be allowed without user interaction. + */ + private void setAllowedConnectionTime(long connectionTime) { + Settings.Global.putLong(mContext.getContentResolver(), + Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime); + }; + + @Test + public void testAllowNewKeyOnce() throws Exception { + // Verifies the behavior when a new key first attempts to connect to a device. During the + // first connection the ADB confirmation activity should be launched to prompt the user to + // allow the connection with an option to always allow connections from this key. + + // Verify if the user allows the key but does not select the option to 'always + // allow' that the connection is allowed but the key is not stored. + runAdbTest(TEST_KEY_1, true, false, false); + } + + @Test + public void testDenyNewKey() throws Exception { + // Verifies if the user does not allow the key then the connection is not allowed and the + // key is not stored. + runAdbTest(TEST_KEY_1, false, false, false); + } + + @Test + public void testDisconnectAlwaysAllowKey() throws Exception { + // When a key is disconnected from a device ADB should send a disconnect message; this + // message should trigger an update of the last connection time for the currently connected + // key. + + // Allow a connection from a new key with the 'Always allow' option selected. + runAdbTest(TEST_KEY_1, true, true, false); + + // Get the last connection time for the currently connected key to verify that it is updated + // after the disconnect. + long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); + + // Sleep for a small amount of time to ensure a difference can be observed in the last + // connection time after a disconnect. + Thread.sleep(10); + + // Send the disconnect message for the currently connected key to trigger an update of the + // last connection time. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget(); + + // Use a latch to ensure the test does not exit untill the Runnable has been processed. + CountDownLatch latch = new CountDownLatch(1); + + // Post a new Runnable to the handler to ensure it runs after the disconnect message is + // processed. + mHandler.post(() -> { + assertNotEquals( + "The last connection time was not updated after the disconnect", + lastConnectionTime, + mKeyStore.getLastConnectionTime(TEST_KEY_1)); + latch.countDown(); + }); + if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("The Runnable to verify the last connection time was updated did not complete " + + "within the timeout period"); + } + } + + @Test + public void testDisconnectAllowedOnceKey() throws Exception { + // When a key is disconnected ADB should send a disconnect message; this message should + // essentially result in a noop for keys that the user only allows once since the last + // connection time is not maintained for these keys. + + // Allow a connection from a new key with the 'Always allow' option set to false + runAdbTest(TEST_KEY_1, true, false, false); + + // Send the disconnect message for the currently connected key. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget(); + + // Verify that the disconnected key is not automatically allowed on a subsequent connection. + runAdbTest(TEST_KEY_1, true, false, false); + } + + @Test + public void testAlwaysAllowConnectionFromKey() throws Exception { + // Verifies when the user selects the 'Always allow' option for the current key that + // subsequent connection attempts from that key are allowed. + + // Allow a connection from a new key with the 'Always allow' option selected. + runAdbTest(TEST_KEY_1, true, true, false); + + // Next attempt another connection with the same key and verify that the activity to prompt + // the user to accept the key is not launched. + runAdbTest(TEST_KEY_1, true, true, true); + + // Verify that a different key is not automatically allowed. + runAdbTest(TEST_KEY_2, false, false, false); + } + + @Test + public void testOriginalAlwaysAllowBehavior() throws Exception { + // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original + // behavior of 'Always allow' should be restored. + + // Accept the test key with the 'Always allow' option selected. + runAdbTest(TEST_KEY_1, true, true, false); + + // Set the connection time to 0 to restore the original behavior. + setAllowedConnectionTime(0); + + // Set the last connection time to the test key to a very small value to ensure it would + // fail the new test but would be allowed with the original behavior. + mKeyStore.setLastConnectionTime(TEST_KEY_1, 1); + + // Run the test with the key and verify that the connection is automatically allowed. + runAdbTest(TEST_KEY_1, true, true, true); + } + + @Test + public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception { + // If a development device is left connected to a system beyond the allowed connection time + // a reboot of the device while connected could make it appear as though the last connection + // time is beyond the allowed window. A scheduled job runs daily while a key is connected + // to update the last connection time to the current time; this ensures if the device is + // rebooted while connected to a system the last connection time should be within 24 hours. + + // Allow the key to connect with the 'Always allow' option selected + runAdbTest(TEST_KEY_1, true, true, false); + + // Get the current last connection time for comparison after the scheduled job is run + long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); + + // Sleep a small amount of time to ensure that the updated connection time changes + Thread.sleep(10); + + // Send a message to the handler to update the last connection time for the active key + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME) + .sendToTarget(); + + // Post a Runnable to the handler to ensure it runs after the update key connection time + // message is processed. + CountDownLatch latch = new CountDownLatch(1); + mHandler.post(() -> { + assertNotEquals( + "The last connection time of the key was not updated after the update key " + + "connection time message", + lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1)); + latch.countDown(); + }); + if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("The Runnable to verify the last connection time was updated did not complete " + + "within the timeout period"); + } + } + + @Test + public void testKeystorePersisted() throws Exception { + // After any updates are made to the key store a message should be sent to persist the + // key store. This test verifies that a key that is always allowed is persisted in the key + // store along with its last connection time. + + // Allow the key to connect with the 'Always allow' option selected + runAdbTest(TEST_KEY_1, true, true, false); + + // Send a message to the handler to persist the updated keystore. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE) + .sendToTarget(); + + // Post a Runnable to the handler to ensure the persist key store message has been processed + // using a new AdbKeyStore backed by the key file. + mHandler.post(() -> assertTrue( + "The key with the 'Always allow' option selected was not persisted in the keystore", + mManager.new AdbKeyStore(mKeyFile).isKeyAuthorized(TEST_KEY_1))); + + // Get the current last connection time to ensure it is updated in the persisted keystore. + long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); + + // Sleep a small amount of time to ensure the last connection time is updated. + Thread.sleep(10); + + // Send a message to the handler to update the last connection time for the active key. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME) + .sendToTarget(); + + // Persist the updated last connection time. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE) + .sendToTarget(); + + // Post a Runnable with a new key store backed by the key file to verify that the last + // connection time obtained above is different from the persisted updated value. + CountDownLatch latch = new CountDownLatch(1); + mHandler.post(() -> { + assertNotEquals( + "The last connection time in the key file was not updated after the update " + + "connection time message", lastConnectionTime, + mManager.new AdbKeyStore(mKeyFile).getLastConnectionTime(TEST_KEY_1)); + latch.countDown(); + }); + if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("The Runnable to verify the last connection time was updated did not complete " + + "within the timeout period"); + } + } + + @Test + public void testAdbClearRemovesActiveKey() throws Exception { + // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always + // allow' key is connected that key should be deleted as well. + + // Allow the key to connect with the 'Always allow' option selected + runAdbTest(TEST_KEY_1, true, true, false); + + // Send a message to the handler to clear the adb authorizations. + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget(); + + // Send a message to disconnect the currently connected key + mHandler.obtainMessage( + AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget(); + + // Post a Runnable to ensure the disconnect has completed to verify the 'Always allow' key + // that was connected when the clear was sent requires authorization. + CountDownLatch latch = new CountDownLatch(1); + mHandler.post(() -> { + assertFalse( + "The currently connected 'always allow' key should not be authorized after an" + + " adb" + + " clear message.", + mKeyStore.isKeyAuthorized(TEST_KEY_1)); + latch.countDown(); + }); + if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) { + fail("The Runnable to verify the key is not authorized did not complete within the " + + "timeout period"); + } + } + + @Test + public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception { + // If the user selects the 'Always allow' option then subsequent connections from the key + // will be allowed as long as the connection is within the allowed window. Once the last + // connection time is beyond this window the user should be prompted to allow the key again. + + // Allow the key to connect with the 'Always allow' option selected + runAdbTest(TEST_KEY_1, true, true, false); + + // Set the allowed window to a small value to ensure the time is beyond the allowed window. + setAllowedConnectionTime(1); + + // Sleep for a small amount of time to exceed the allowed window. + Thread.sleep(10); + + // A new connection from this key should prompt the user again. + runAdbTest(TEST_KEY_1, true, true, false); + } + + @Test + public void testLastConnectionTimeCannotBeSetBack() throws Exception { + // When a device is first booted there is a possibility that the system time will be set to + // the build time of the system image. If a device is connected to a system during a reboot + // this could cause the connection time to be set in the past; if the device time is not + // corrected before the device is disconnected then a subsequent connection with the time + // corrected would appear as though the last connection time was beyond the allowed window, + // and the user would be required to authorize the connection again. This test verifies that + // the AdbKeyStore does not update the last connection time if it is less than the + // previously written connection time. + + // Allow the key to connect with the 'Always allow' option selected + runAdbTest(TEST_KEY_1, true, true, false); + + // Get the last connection time that was written to the key store. + long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); + + // Attempt to set the last connection time to 1970 + mKeyStore.setLastConnectionTime(TEST_KEY_1, 0); + assertEquals( + "The last connection time in the adb key store should not be set to a value less " + + "than the previous connection time", + lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1)); + + // Attempt to set the last connection time just beyond the allowed window. + mKeyStore.setLastConnectionTime(TEST_KEY_1, + Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1))); + assertEquals( + "The last connection time in the adb key store should not be set to a value less " + + "than the previous connection time", + lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1)); + } + + /** + * Runs an adb test with the provided configuration. + * + * @param key The base64 encoding of the key to be used during the test. + * @param allowKey boolean indicating whether the key should be allowed to connect. + * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected. + * @param autoAllowExpected boolean indicating whether the key is expected to be automatically + * allowed without user interaction. + */ + private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow, + boolean autoAllowExpected) throws Exception { + // if the key should not be automatically allowed then set up the activity + if (!autoAllowExpected) { + new AdbDebuggingManagerTestActivity.Configurator() + .setExpectedKey(key) + .setAllowKey(allowKey) + .setAlwaysAllow(alwaysAllow) + .setHandler(mHandler) + .setBlockingQueue(mBlockingQueue); + } + // send the message indicating a new key is attempting to connect + mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM, + key).sendToTarget(); + // if the key should not be automatically allowed then the ADB public key confirmation + // activity should be launched + if (!autoAllowExpected) { + TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT); + assertNotNull( + "The ADB public key confirmation activity did not complete within the timeout" + + " period", activityResult); + assertEquals("The ADB public key activity failed with result: " + activityResult, + TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode); + } + // If the activity was launched it should send a response back to the manager that would + // trigger a response to the thread, or if the key is a known valid key then a response + // should be sent back without requiring interaction with the activity. + TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT); + assertNotNull("A response was not sent to the thread within the timeout period", + threadResult); + // verify that the result is an expected message from the thread + assertEquals("An unexpected result was received: " + threadResult, + TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode); + assertEquals("The manager did not send the proper response for allowKey = " + allowKey, + allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage); + // if the key is not allowed or not always allowed verify it is not in the key store + if (!allowKey || !alwaysAllow) { + assertFalse( + "The key should not be allowed automatically on subsequent connection attempts", + mKeyStore.isKeyAuthorized(key)); + } + } + + /** + * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager + * indicating whether the key should be allowed to connect. + */ + class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread { + AdbDebuggingThreadTest() { + mManager.super(); + } + + @Override + public void sendResponse(String msg) { + TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg); + try { + mBlockingQueue.put(result); + } catch (InterruptedException e) { + Log.e(TAG, + "Caught an InterruptedException putting the result in the queue: " + result, + e); + } + } + } + + /** + * Contains the result for the current portion of the test along with any corresponding + * messages. + */ + public static class TestResult { + public int mReturnCode; + public String mMessage; + + public static final int RESULT_ACTIVITY_LAUNCHED = 1; + public static final int RESULT_UNEXPECTED_KEY = 2; + public static final int RESULT_RESPONSE_RECEIVED = 3; + + public TestResult(int returnCode) { + this(returnCode, null); + } + + public TestResult(int returnCode, String message) { + mReturnCode = returnCode; + mMessage = message; + } + + @Override + public String toString() { + return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}"; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java new file mode 100644 index 000000000000..1a9c180ffa98 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 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.adb; + +import static com.android.server.adb.AdbDebuggingManagerTest.TestResult; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.util.Log; + +import java.util.concurrent.BlockingQueue; + +/** + * Helper Activity used to test the AdbDebuggingManager's prompt to allow an adb key. + */ +public class AdbDebuggingManagerTestActivity extends Activity { + + private static final String TAG = "AdbDebuggingManagerTestActivity"; + + /* + * Static values that must be set before each test to modify the behavior of the Activity. + */ + private static AdbDebuggingManager.AdbDebuggingHandler sHandler; + private static boolean sAllowKey; + private static boolean sAlwaysAllow; + private static String sExpectedKey; + private static BlockingQueue sBlockingQueue; + + /** + * Receives the Intent sent from the AdbDebuggingManager and sends the preconfigured response. + */ + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + Intent intent = getIntent(); + String key = intent.getStringExtra("key"); + if (!key.equals(sExpectedKey)) { + TestResult result = new TestResult(TestResult.RESULT_UNEXPECTED_KEY, key); + postResult(result); + finish(); + return; + } + // Post the result that the activity was successfully launched as expected and a response + // is being sent to let the test method know that it should move on to waiting for the next + // expected response from the AdbDebuggingManager. + TestResult result = new TestResult( + AdbDebuggingManagerTest.TestResult.RESULT_ACTIVITY_LAUNCHED); + postResult(result); + + // Initialize the message based on the preconfigured values. If the key is accepted the + // AdbDebuggingManager expects the key to be in the obj field of the message, and if the + // user selects the 'Always allow' option the manager expects the arg1 field to be set to 1. + int messageType; + if (sAllowKey) { + messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_ALLOW; + } else { + messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DENY; + } + Message message = sHandler.obtainMessage(messageType); + message.obj = key; + if (sAlwaysAllow) { + message.arg1 = 1; + } + finish(); + sHandler.sendMessage(message); + } + + /** + * Posts the result of the activity to the test method. + */ + private void postResult(TestResult result) { + try { + sBlockingQueue.put(result); + } catch (InterruptedException e) { + Log.e(TAG, "Caught an InterruptedException posting the result " + result, e); + } + } + + /** + * Allows test methods to specify the behavior of the Activity before it is invoked by the + * AdbDebuggingManager. + */ + public static class Configurator { + + /** + * Sets the test handler to be used by this activity to send the configured response. + */ + public Configurator setHandler(AdbDebuggingManager.AdbDebuggingHandler handler) { + sHandler = handler; + return this; + } + + /** + * Sets whether the key should be allowed for this test. + */ + public Configurator setAllowKey(boolean allow) { + sAllowKey = allow; + return this; + } + + /** + * Sets whether the 'Always allow' option should be selected for this test. + */ + public Configurator setAlwaysAllow(boolean alwaysAllow) { + sAlwaysAllow = alwaysAllow; + return this; + } + + /** + * Sets the key that should be expected from the AdbDebuggingManager for this test. + */ + public Configurator setExpectedKey(String expectedKey) { + sExpectedKey = expectedKey; + return this; + } + + /** + * Sets the BlockingQueue that should be used to post the result of the Activity back to the + * test method. + */ + public Configurator setBlockingQueue(BlockingQueue blockingQueue) { + sBlockingQueue = blockingQueue; + return this; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java new file mode 100644 index 000000000000..1a231cf56921 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; + +import static com.android.server.am.ActivityManagerService.Injector; +import static com.android.server.am.AppCompactor.compactActionIntToString; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.os.Handler; +import android.os.HandlerThread; +import android.provider.DeviceConfig; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.appop.AppOpsService; + +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 java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link AppCompactor}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:AppCompactorTest + */ +@RunWith(AndroidJUnit4.class) +public final class AppCompactorTest { + + @Mock private AppOpsService mAppOpsService; + private AppCompactor mCompactorUnderTest; + private HandlerThread mHandlerThread; + private Handler mHandler; + private CountDownLatch mCountDown; + + private static void clearDeviceConfig() throws IOException { + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_USE_COMPACTION); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4); + } + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + clearDeviceConfig(); + mHandlerThread = new HandlerThread(""); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + ActivityManagerService ams = new ActivityManagerService(new TestInjector()); + mCompactorUnderTest = new AppCompactor(ams, + new AppCompactor.PropertyChangedCallbackForTest() { + @Override + public void onPropertyChanged() { + if (mCountDown != null) { + mCountDown.countDown(); + } + } + }); + } + + @After + public void tearDown() throws IOException { + mHandlerThread.quit(); + mCountDown = null; + clearDeviceConfig(); + } + + @Test + public void init_setsDefaults() { + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + assertThat(mCompactorUnderTest.mCompactActionSome, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + @Test + public void init_withDeviceConfigSetsParameters() { + // When the DeviceConfig already has a flag value stored (note this test will need to + // change if the default value changes from false). + assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false)); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + + // Then calling init will read and set that flag. + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + // When we call init and change some the flag value... + mCompactorUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then that new flag value is updated in the implementation. + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + // And again, setting the flag the other way. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "false", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.useCompaction(), is(false)); + } + + @Test + public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + mCompactorUnderTest.init(); + + // When we push an invalid flag value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "foobar", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then we set the default. + assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION)); + } + + @Test + public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with reasonable values... + + // There are three possible values for compactAction[Some|Full]. + for (int i = 1; i < 4; i++) { + mCountDown = new CountDownLatch(2); + int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); + int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the updates are reflected in the flags. + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(expectedSome))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(expectedFull))); + } + } + + @Test + public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with bad values ... + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "foo", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the default values are reflected in the flag + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + } + + @Test + public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new reasonable throttle values after init... + mCountDown = new CountDownLatch(4); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then those flags values are reflected in the compactor. + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void compactThrottle_listensToDeviceConfigChangesBadValues() + throws IOException, InterruptedException { + mCompactorUnderTest.init(); + + // When one of the throttles is overridden with a bad value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, "foo", false); + // Then all the throttles have the defaults set. + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + // Repeat for each of the throttle keys. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + private class TestInjector extends Injector { + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index ac4a5fe90c06..a3f36b720398 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -24,11 +24,15 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.Manifest; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; @@ -39,21 +43,21 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.test.mock.MockContentResolver; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.test.FakeSettingsProvider; - +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -99,10 +103,6 @@ public class TrampolineTest { @Mock private Context mContextMock; @Mock - private File mSuppressFileMock; - @Mock - private File mSuppressFileParentMock; - @Mock private IBinder mAgentMock; @Mock private ParcelFileDescriptor mParcelFileDescriptorMock; @@ -114,181 +114,232 @@ public class TrampolineTest { private IBackupManagerMonitor mBackupManagerMonitorMock; @Mock private PrintWriter mPrintWriterMock; + @Mock + private UserManager mUserManagerMock; + @Mock + private UserInfo mUserInfoMock; private FileDescriptor mFileDescriptorStub = new FileDescriptor(); private TrampolineTestable mTrampoline; - private MockContentResolver mContentResolver; + private File mTestDir; + private File mSuppressFile; + private File mActivatedFile; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mUserId = NON_USER_SYSTEM; + mUserId = UserHandle.USER_SYSTEM; SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>(); - serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService); + serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService); serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService); when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers); + when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); + when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); + TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock; TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM; TrampolineTestable.sCallingUid = Process.SYSTEM_UID; TrampolineTestable.sBackupDisabled = false; + TrampolineTestable.sUserManagerMock = mUserManagerMock; + + mTestDir = InstrumentationRegistry.getContext().getFilesDir(); + mTestDir.mkdirs(); - when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock); + mSuppressFile = new File(mTestDir, "suppress"); + TrampolineTestable.sSuppressFile = mSuppressFile; + + mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM); + TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile); mTrampoline = new TrampolineTestable(mContextMock); + } - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContextMock.getContentResolver()).thenReturn(mContentResolver); + @After + public void tearDown() throws Exception { + mSuppressFile.delete(); + mActivatedFile.delete(); } @Test - public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void initializeService_successfullyInitializesBackupService() { mTrampoline.initializeService(); - mTrampoline.unlockUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); - mTrampoline.initializeService(); + public void initializeService_globallyDisabled_nonInitialized() { + TrampolineTestable.sBackupDisabled = true; + TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - mTrampoline.unlockUser(NON_USER_SYSTEM); + trampoline.initializeService(); - verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM); + assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); + public void initializeService_doesNotStartServiceForUsers() { mTrampoline.initializeService(); - mTrampoline.unlockUser(NON_USER_SYSTEM); + verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); + } - verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM); + @Test + public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - mTrampoline.stopUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); - + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_successfullyInitializesBackupService() { + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + // Don't activate non-system user. - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_globallyDisabled_nonInitialized() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - - trampoline.initializeService(); + public void + isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated() + throws Exception { + mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } - // Verify that BackupManagerService is not initialized if suppress file exists. @Test - public void initializeService_suppressFileExists_nonInitialized() throws Exception { - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM); - + public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - trampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void initializeService_doesNotStartServiceForUsers() { + public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() { mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.ROOT_UID; - verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); - } + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - @Test - public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + try { + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test - public void setBackupServiceActive_callerSystemUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerRootUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.ROOT_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() { + public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; try { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } + } - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } + } + + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test public void setBackupServiceActive_backupDisabled_ignored() { TrampolineTestable.sBackupDisabled = true; TrampolineTestable trampoline = new TrampolineTestable(mContextMock); + trampoline.initializeService(); trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); @@ -296,14 +347,8 @@ public class TrampolineTest { } @Test - public void setBackupServiceActive_nonUserSystem_ignored() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); - } - - @Test public void setBackupServiceActive_alreadyActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); assertEquals(1, mTrampoline.getCreateServiceCallsCount()); @@ -315,6 +360,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); @@ -323,6 +369,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); @@ -349,6 +396,21 @@ public class TrampolineTest { } @Test + public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() { + mTrampoline.initializeService(); + int otherUser = NON_USER_SYSTEM + 1; + File activateFile = new File(mTestDir, "activate-" + otherUser); + TrampolineTestable.sActivatedFiles.append(otherUser, activateFile); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(otherUser)); + activateFile.delete(); + } + + @Test public void dataChanged_calledBeforeInitialize_ignored() throws Exception { mTrampoline.dataChanged(PACKAGE_NAME); verifyNoMoreInteractions(mBackupManagerServiceMock); @@ -1123,7 +1185,7 @@ public class TrampolineTest { @Test public void requestBackup_forwardedToCallingUserId() throws Exception { TrampolineTestable.sCallingUserId = mUserId; - when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES, + when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES, mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456); mTrampoline.initializeService(); @@ -1227,50 +1289,33 @@ public class TrampolineTest { static int sCallingUserId = -1; static int sCallingUid = -1; static BackupManagerService sBackupManagerServiceMock = null; + static File sSuppressFile = null; + static SparseArray<File> sActivatedFiles = new SparseArray<>(); + static UserManager sUserManagerMock = null; private int mCreateServiceCallsCount = 0; - private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>(); - - private static class FakeFile extends File { - private boolean mExists; - - FakeFile(String pathname) { - super(pathname); - } - - @Override - public boolean exists() { - return mExists; - } - - @Override - public boolean delete() { - mExists = false; - return true; - } - - @Override - public boolean createNewFile() throws IOException { - mExists = true; - return true; - } - } TrampolineTestable(Context context) { super(context); } @Override + protected UserManager getUserManager() { + return sUserManagerMock; + } + + @Override public boolean isBackupDisabled() { return sBackupDisabled; } @Override - public File getSuppressFileForUser(int userId) { - if (mSuppressFiles.get(userId) == null) { - FakeFile file = new FakeFile(Integer.toString(userId)); - mSuppressFiles.append(userId, file); - } - return mSuppressFiles.get(userId); + protected File getSuppressFileForSystemUser() { + return sSuppressFile; + } + + @Override + protected File getActivatedFileForNonSystemUser(int userId) { + return sActivatedFiles.get(userId); } protected int binderGetCallingUserId() { @@ -1289,11 +1334,6 @@ public class TrampolineTest { } @Override - protected void createBackupSuppressFileForUser(int userId) throws IOException { - getSuppressFileForUser(userId).createNewFile(); - } - - @Override protected void postToHandler(Runnable runnable) { runnable.run(); } diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java new file mode 100644 index 000000000000..095a1deac912 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java @@ -0,0 +1,1172 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testutils; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ChangedPackages; +import android.content.pm.IDexModuleRegisterCallback; +import android.content.pm.IOnPermissionsChangeListener; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageDeleteObserver2; +import android.content.pm.IPackageInstaller; +import android.content.pm.IPackageManager; +import android.content.pm.IPackageMoveObserver; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstrumentationInfo; +import android.content.pm.KeySet; +import android.content.pm.ModuleInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.SuspendDialogInfo; +import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.VersionedPackage; +import android.content.pm.dex.IArtManager; +import android.graphics.Bitmap; +import android.os.IBinder; +import android.os.PersistableBundle; +import android.os.RemoteException; +import java.util.List; + +/** + * Stub for IPackageManager to use in tests. + */ +public class IPackageManagerStub implements IPackageManager { + public static PackageInfo sPackageInfo; + public static int sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + + @Override + public PackageInfo getPackageInfo(String packageName, int flags, int userId) + throws RemoteException { + return sPackageInfo; + } + + @Override + public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException { + return sApplicationEnabledSetting; + } + + @Override + public void checkPackageStartable(String packageName, int userId) throws RemoteException { + + } + + @Override + public boolean isPackageAvailable(String packageName, int userId) throws RemoteException { + return false; + } + + @Override + public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, int flags, + int userId) throws RemoteException { + return null; + } + + @Override + public int getPackageUid(String packageName, int flags, int userId) throws RemoteException { + return 0; + } + + @Override + public int[] getPackageGids(String packageName, int flags, int userId) throws RemoteException { + return new int[0]; + } + + @Override + public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException { + return new String[0]; + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException { + return new String[0]; + } + + @Override + public PermissionInfo getPermissionInfo(String name, String packageName, int flags) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryPermissionsByGroup(String group, int flags) + throws RemoteException { + return null; + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getAllPermissionGroups(int flags) throws RemoteException { + return null; + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public boolean activitySupportsIntent(ComponentName className, Intent intent, + String resolvedType) + throws RemoteException { + return false; + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public int checkPermission(String permName, String pkgName, int userId) throws RemoteException { + return 0; + } + + @Override + public int checkUidPermission(String permName, int uid) throws RemoteException { + return 0; + } + + @Override + public boolean addPermission(PermissionInfo info) throws RemoteException { + return false; + } + + @Override + public void removePermission(String name) throws RemoteException { + + } + + @Override + public void grantRuntimePermission(String packageName, String permissionName, int userId) + throws RemoteException { + + } + + @Override + public void revokeRuntimePermission(String packageName, String permissionName, int userId) + throws RemoteException { + + } + + @Override + public void resetRuntimePermissions() throws RemoteException { + + } + + @Override + public int getPermissionFlags(String permissionName, String packageName, int userId) + throws RemoteException { + return 0; + } + + @Override + public void updatePermissionFlags(String permissionName, String packageName, int flagMask, + int flagValues, int userId) throws RemoteException { + + } + + @Override + public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) + throws RemoteException { + + } + + @Override + public boolean shouldShowRequestPermissionRationale(String permissionName, String packageName, + int userId) throws RemoteException { + return false; + } + + @Override + public boolean isProtectedBroadcast(String actionName) throws RemoteException { + return false; + } + + @Override + public int checkSignatures(String pkg1, String pkg2) throws RemoteException { + return 0; + } + + @Override + public int checkUidSignatures(int uid1, int uid2) throws RemoteException { + return 0; + } + + @Override + public List<String> getAllPackages() throws RemoteException { + return null; + } + + @Override + public String[] getPackagesForUid(int uid) throws RemoteException { + return new String[0]; + } + + @Override + public String getNameForUid(int uid) throws RemoteException { + return null; + } + + @Override + public String[] getNamesForUids(int[] uids) throws RemoteException { + return new String[0]; + } + + @Override + public int getUidForSharedUser(String sharedUserName) throws RemoteException { + return 0; + } + + @Override + public int getFlagsForUid(int uid) throws RemoteException { + return 0; + } + + @Override + public int getPrivateFlagsForUid(int uid) throws RemoteException { + return 0; + } + + @Override + public boolean isUidPrivileged(int uid) throws RemoteException { + return false; + } + + @Override + public String[] getAppOpPermissionPackages(String permissionName) throws RemoteException { + return new String[0]; + } + + @Override + public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) + throws RemoteException { + return null; + } + + @Override + public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId, + int targetUserId) throws RemoteException { + return false; + } + + @Override + public ParceledListSlice queryIntentActivities(Intent intent, String resolvedType, int flags, + int userId) throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryIntentActivityOptions(ComponentName caller, Intent[] specifics, + String[] specificTypes, Intent intent, String resolvedType, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryIntentReceivers(Intent intent, String resolvedType, int flags, + int userId) throws RemoteException { + return null; + } + + @Override + public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryIntentServices(Intent intent, String resolvedType, int flags, + int userId) throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryIntentContentProviders(Intent intent, String resolvedType, + int flags, int userId) throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags, + int userId) throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getInstalledApplications(int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getPersistentApplications(int flags) throws RemoteException { + return null; + } + + @Override + public ProviderInfo resolveContentProvider(String name, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) + throws RemoteException { + + } + + @Override + public ParceledListSlice queryContentProviders(String processName, int uid, int flags, + String metaDataKey) throws RemoteException { + return null; + } + + @Override + public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice queryInstrumentation(String targetPackage, int flags) + throws RemoteException { + return null; + } + + @Override + public void finishPackageInstall(int token, boolean didLaunch) throws RemoteException { + + } + + @Override + public void setInstallerPackageName(String targetPackage, String installerPackageName) + throws RemoteException { + + } + + @Override + public void setApplicationCategoryHint(String packageName, int categoryHint, + String callerPackageName) throws RemoteException { + + } + + @Override + public void deletePackageAsUser(String packageName, int versionCode, + IPackageDeleteObserver observer, int userId, int flags) throws RemoteException { + + } + + @Override + public void deletePackageVersioned(VersionedPackage versionedPackage, + IPackageDeleteObserver2 observer, int userId, int flags) throws RemoteException { + + } + + @Override + public String getInstallerPackageName(String packageName) throws RemoteException { + return null; + } + + @Override + public void resetApplicationPreferences(int userId) throws RemoteException { + + } + + @Override + public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) + throws RemoteException { + return null; + } + + @Override + public void setLastChosenActivity(Intent intent, String resolvedType, int flags, + IntentFilter filter, int match, ComponentName activity) throws RemoteException { + + } + + @Override + public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, + ComponentName activity, int userId) throws RemoteException { + + } + + @Override + public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set, + ComponentName activity, int userId) throws RemoteException { + + } + + @Override + public void clearPackagePreferredActivities(String packageName) throws RemoteException { + + } + + @Override + public int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) throws RemoteException { + return 0; + } + + @Override + public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity, + int userId) throws RemoteException { + + } + + @Override + public void clearPackagePersistentPreferredActivities(String packageName, int userId) + throws RemoteException { + + } + + @Override + public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage, + int sourceUserId, int targetUserId, int flags) throws RemoteException { + + } + + @Override + public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) + throws RemoteException { + + } + + @Override + public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames, + int restrictionFlags, int userId) throws RemoteException { + return new String[0]; + } + + @Override + public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo, + String callingPackage, int userId) throws RemoteException { + return new String[0]; + } + + @Override + public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) + throws RemoteException { + return new String[0]; + } + + @Override + public boolean isPackageSuspendedForUser(String packageName, int userId) + throws RemoteException { + return false; + } + + @Override + public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId) + throws RemoteException { + return null; + } + + @Override + public byte[] getPreferredActivityBackup(int userId) throws RemoteException { + return new byte[0]; + } + + @Override + public void restorePreferredActivities(byte[] backup, int userId) throws RemoteException { + + } + + @Override + public byte[] getDefaultAppsBackup(int userId) throws RemoteException { + return new byte[0]; + } + + @Override + public void restoreDefaultApps(byte[] backup, int userId) throws RemoteException { + + } + + @Override + public byte[] getIntentFilterVerificationBackup(int userId) throws RemoteException { + return new byte[0]; + } + + @Override + public void restoreIntentFilterVerification(byte[] backup, int userId) throws RemoteException { + + } + + @Override + public byte[] getPermissionGrantBackup(int userId) throws RemoteException { + return new byte[0]; + } + + @Override + public void restorePermissionGrants(byte[] backup, int userId) throws RemoteException { + + } + + @Override + public ComponentName getHomeActivities(List<ResolveInfo> outHomeCandidates) + throws RemoteException { + return null; + } + + @Override + public void setHomeActivity(ComponentName className, int userId) throws RemoteException { + + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, + int userId) throws RemoteException { + + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName, int userId) + throws RemoteException { + return 0; + } + + @Override + public void setApplicationEnabledSetting(String packageName, int newState, int flags, + int userId, + String callingPackage) throws RemoteException { + + } + + @Override + public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, + String apkFile, + int pid) throws RemoteException { + + } + + @Override + public void flushPackageRestrictionsAsUser(int userId) throws RemoteException { + + } + + @Override + public void setPackageStoppedState(String packageName, boolean stopped, int userId) + throws RemoteException { + + } + + @Override + public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, int storageFlags, + IPackageDataObserver observer) throws RemoteException { + + } + + @Override + public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags, + IntentSender pi) throws RemoteException { + + } + + @Override + public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) + throws RemoteException { + + } + + @Override + public void deleteApplicationCacheFilesAsUser(String packageName, int userId, + IPackageDataObserver observer) throws RemoteException { + + } + + @Override + public void clearApplicationUserData(String packageName, IPackageDataObserver observer, + int userId) throws RemoteException { + + } + + @Override + public void clearApplicationProfileData(String packageName) throws RemoteException { + + } + + @Override + public void getPackageSizeInfo(String packageName, int userHandle, + IPackageStatsObserver observer) + throws RemoteException { + + } + + @Override + public String[] getSystemSharedLibraryNames() throws RemoteException { + return new String[0]; + } + + @Override + public ParceledListSlice getSystemAvailableFeatures() throws RemoteException { + return null; + } + + @Override + public boolean hasSystemFeature(String name, int version) throws RemoteException { + return false; + } + + @Override + public void enterSafeMode() throws RemoteException { + + } + + @Override + public boolean isSafeMode() throws RemoteException { + return false; + } + + @Override + public void systemReady() throws RemoteException { + + } + + @Override + public boolean hasSystemUidErrors() throws RemoteException { + return false; + } + + @Override + public void performFstrimIfNeeded() throws RemoteException { + + } + + @Override + public void updatePackagesIfNeeded() throws RemoteException { + + } + + @Override + public void notifyPackageUse(String packageName, int reason) throws RemoteException { + + } + + @Override + public void notifyDexLoad(String loadingPackageName, List<String> classLoadersNames, + List<String> classPaths, String loaderIsa) throws RemoteException { + + } + + @Override + public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule, + IDexModuleRegisterCallback callback) throws RemoteException { + + } + + @Override + public boolean performDexOptMode(String packageName, boolean checkProfiles, + String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) + throws RemoteException { + return false; + } + + @Override + public boolean performDexOptSecondary(String packageName, String targetCompilerFilter, + boolean force) throws RemoteException { + return false; + } + + @Override + public boolean compileLayouts(String packageName) throws RemoteException { + return false; + } + + @Override + public void dumpProfiles(String packageName) throws RemoteException { + + } + + @Override + public void forceDexOpt(String packageName) throws RemoteException { + + } + + @Override + public boolean runBackgroundDexoptJob(List<String> packageNames) throws RemoteException { + return false; + } + + @Override + public void reconcileSecondaryDexFiles(String packageName) throws RemoteException { + + } + + @Override + public int getMoveStatus(int moveId) throws RemoteException { + return 0; + } + + @Override + public void registerMoveCallback(IPackageMoveObserver callback) throws RemoteException { + + } + + @Override + public void unregisterMoveCallback(IPackageMoveObserver callback) throws RemoteException { + + } + + @Override + public int movePackage(String packageName, String volumeUuid) throws RemoteException { + return 0; + } + + @Override + public int movePrimaryStorage(String volumeUuid) throws RemoteException { + return 0; + } + + @Override + public boolean addPermissionAsync(PermissionInfo info) throws RemoteException { + return false; + } + + @Override + public boolean setInstallLocation(int loc) throws RemoteException { + return false; + } + + @Override + public int getInstallLocation() throws RemoteException { + return 0; + } + + @Override + public int installExistingPackageAsUser(String packageName, int userId, int installFlags, + int installReason) throws RemoteException { + return 0; + } + + @Override + public void verifyPendingInstall(int id, int verificationCode) throws RemoteException { + + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) throws RemoteException { + + } + + @Override + public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) + throws RemoteException { + + } + + @Override + public int getIntentVerificationStatus(String packageName, int userId) throws RemoteException { + return 0; + } + + @Override + public boolean updateIntentVerificationStatus(String packageName, int status, int userId) + throws RemoteException { + return false; + } + + @Override + public ParceledListSlice getIntentFilterVerifications(String packageName) + throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getAllIntentFilters(String packageName) throws RemoteException { + return null; + } + + @Override + public boolean setDefaultBrowserPackageName(String packageName, int userId) + throws RemoteException { + return false; + } + + @Override + public String getDefaultBrowserPackageName(int userId) throws RemoteException { + return null; + } + + @Override + public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { + return null; + } + + @Override + public boolean isFirstBoot() throws RemoteException { + return false; + } + + @Override + public boolean isOnlyCoreApps() throws RemoteException { + return false; + } + + @Override + public boolean isUpgrade() throws RemoteException { + return false; + } + + @Override + public void setPermissionEnforced(String permission, boolean enforced) throws RemoteException { + + } + + @Override + public boolean isPermissionEnforced(String permission) throws RemoteException { + return false; + } + + @Override + public boolean isStorageLow() throws RemoteException { + return false; + } + + @Override + public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId) + throws RemoteException { + return false; + } + + @Override + public boolean getApplicationHiddenSettingAsUser(String packageName, int userId) + throws RemoteException { + return false; + } + + @Override + public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden) + throws RemoteException { + + } + + @Override + public boolean setSystemAppInstallState(String packageName, boolean installed, int userId) + throws RemoteException { + return false; + } + + @Override + public IPackageInstaller getPackageInstaller() throws RemoteException { + return null; + } + + @Override + public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId) + throws RemoteException { + return false; + } + + @Override + public boolean getBlockUninstallForUser(String packageName, int userId) throws RemoteException { + return false; + } + + @Override + public KeySet getKeySetByAlias(String packageName, String alias) throws RemoteException { + return null; + } + + @Override + public KeySet getSigningKeySet(String packageName) throws RemoteException { + return null; + } + + @Override + public boolean isPackageSignedByKeySet(String packageName, KeySet ks) throws RemoteException { + return false; + } + + @Override + public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks) + throws RemoteException { + return false; + } + + @Override + public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) + throws RemoteException { + + } + + @Override + public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) + throws RemoteException { + + } + + @Override + public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId) + throws RemoteException { + + } + + @Override + public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) + throws RemoteException { + + } + + @Override + public void grantDefaultPermissionsToEnabledTelephonyDataServices(String[] packageNames, + int userId) throws RemoteException { + + } + + @Override + public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(String[] packageNames, + int userId) throws RemoteException { + + } + + @Override + public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId) + throws RemoteException { + + } + + @Override + public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId) + throws RemoteException { + + } + + @Override + public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId) + throws RemoteException { + return false; + } + + @Override + public String getPermissionControllerPackageName() throws RemoteException { + return null; + } + + @Override + public ParceledListSlice getInstantApps(int userId) throws RemoteException { + return null; + } + + @Override + public byte[] getInstantAppCookie(String packageName, int userId) throws RemoteException { + return new byte[0]; + } + + @Override + public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId) + throws RemoteException { + return false; + } + + @Override + public Bitmap getInstantAppIcon(String packageName, int userId) throws RemoteException { + return null; + } + + @Override + public boolean isInstantApp(String packageName, int userId) throws RemoteException { + return false; + } + + @Override + public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp) + throws RemoteException { + return false; + } + + @Override + public void setUpdateAvailable(String packageName, boolean updateAvaialble) + throws RemoteException { + + } + + @Override + public String getServicesSystemSharedLibraryPackageName() throws RemoteException { + return null; + } + + @Override + public String getSharedSystemSharedLibraryPackageName() throws RemoteException { + return null; + } + + @Override + public ChangedPackages getChangedPackages(int sequenceNumber, int userId) + throws RemoteException { + return null; + } + + @Override + public boolean isPackageDeviceAdminOnAnyUser(String packageName) throws RemoteException { + return false; + } + + @Override + public int getInstallReason(String packageName, int userId) throws RemoteException { + return 0; + } + + @Override + public ParceledListSlice getSharedLibraries(String packageName, int flags, int userId) + throws RemoteException { + return null; + } + + @Override + public boolean canRequestPackageInstalls(String packageName, int userId) + throws RemoteException { + return false; + } + + @Override + public void deletePreloadsFileCache() throws RemoteException { + + } + + @Override + public ComponentName getInstantAppResolverComponent() throws RemoteException { + return null; + } + + @Override + public ComponentName getInstantAppResolverSettingsComponent() throws RemoteException { + return null; + } + + @Override + public ComponentName getInstantAppInstallerComponent() throws RemoteException { + return null; + } + + @Override + public String getInstantAppAndroidId(String packageName, int userId) throws RemoteException { + return null; + } + + @Override + public IArtManager getArtManager() throws RemoteException { + return null; + } + + @Override + public void setHarmfulAppWarning(String packageName, CharSequence warning, int userId) + throws RemoteException { + + } + + @Override + public CharSequence getHarmfulAppWarning(String packageName, int userId) + throws RemoteException { + return null; + } + + @Override + public boolean hasSigningCertificate(String packageName, byte[] signingCertificate, int flags) + throws RemoteException { + return false; + } + + @Override + public boolean hasUidSigningCertificate(int uid, byte[] signingCertificate, int flags) + throws RemoteException { + return false; + } + + @Override + public String getSystemTextClassifierPackageName() throws RemoteException { + return null; + } + + @Override + public String getWellbeingPackageName() throws RemoteException { + return null; + } + + @Override + public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException { + return false; + } + + @Override + public void sendDeviceCustomizationReadyBroadcast() throws RemoteException { + + } + + @Override + public List<ModuleInfo> getInstalledModules(int flags) throws RemoteException { + return null; + } + + @Override + public ModuleInfo getModuleInfo(String packageName, int flags) throws RemoteException { + return null; + } + + @Override + public IBinder asBinder() { + return null; + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java index 525135c6fc5a..717275232a81 100644 --- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java +++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java @@ -48,11 +48,7 @@ public class PackageManagerStub extends PackageManager { @Override public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { - if (sPackageInfo == null) { - throw new NameNotFoundException(); - } - - return sPackageInfo; + return getPackageInfoAsUser(packageName, flags, UserHandle.USER_SYSTEM); } @Override @@ -64,7 +60,11 @@ public class PackageManagerStub extends PackageManager { @Override public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) throws NameNotFoundException { - return null; + if (sPackageInfo == null) { + throw new NameNotFoundException(); + } + + return sPackageInfo; } @Override diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java index 479a19b93ca7..a92b576e5d0f 100644 --- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java @@ -29,13 +29,14 @@ import android.content.pm.PackageParser; import android.content.pm.Signature; import android.content.pm.SigningInfo; import android.os.Process; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.backup.UserBackupManagerService; -import com.android.server.backup.testutils.PackageManagerStub; +import com.android.server.backup.testutils.IPackageManagerStub; import org.junit.Before; import org.junit.Test; @@ -53,13 +54,17 @@ public class AppBackupUtilsTest { private static final Signature SIGNATURE_3 = generateSignature((byte) 3); private static final Signature SIGNATURE_4 = generateSignature((byte) 4); - private PackageManagerStub mPackageManagerStub; + private IPackageManagerStub mPackageManagerStub; private PackageManagerInternal mMockPackageManagerInternal; + private int mUserId; + @Before public void setUp() throws Exception { - mPackageManagerStub = new PackageManagerStub(); + mPackageManagerStub = new IPackageManagerStub(); mMockPackageManagerInternal = mock(PackageManagerInternal.class); + + mUserId = UserHandle.USER_SYSTEM; } @Test @@ -71,7 +76,7 @@ public class AppBackupUtilsTest { applicationInfo.packageName = TEST_PACKAGE_NAME; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -86,7 +91,7 @@ public class AppBackupUtilsTest { applicationInfo.packageName = TEST_PACKAGE_NAME; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -100,7 +105,7 @@ public class AppBackupUtilsTest { applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -114,11 +119,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isTrue(); } @@ -132,11 +137,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = null; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isTrue(); } @@ -150,11 +155,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isTrue(); } @@ -168,11 +173,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -186,11 +191,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = null; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -204,11 +209,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo, - mPackageManagerStub); + mPackageManagerStub, mUserId); assertThat(isEligible).isFalse(); } @@ -222,10 +227,11 @@ public class AppBackupUtilsTest { applicationInfo.packageName = TEST_PACKAGE_NAME; applicationInfo.enabled = true; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isFalse(); } @@ -239,10 +245,11 @@ public class AppBackupUtilsTest { applicationInfo.packageName = TEST_PACKAGE_NAME; applicationInfo.enabled = false; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isTrue(); } @@ -255,10 +262,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isFalse(); } @@ -271,10 +279,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isTrue(); } @@ -287,10 +296,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isTrue(); } @@ -303,10 +313,11 @@ public class AppBackupUtilsTest { applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME; applicationInfo.packageName = TEST_PACKAGE_NAME; - PackageManagerStub.sApplicationEnabledSetting = + IPackageManagerStub.sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; - boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub); + boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub, + mUserId); assertThat(isDisabled).isTrue(); } diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java index d43b677d0e50..5fcce671db1e 100644 --- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java @@ -44,6 +44,7 @@ import android.content.pm.Signature; import android.content.pm.SigningInfo; import android.os.Bundle; import android.os.Process; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -88,12 +89,14 @@ public class TarBackupReaderTest { private final PackageManagerStub mPackageManagerStub = new PackageManagerStub(); private Context mContext; + private int mUserId; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getContext(); + mUserId = UserHandle.USER_SYSTEM; } @Test @@ -146,7 +149,7 @@ public class TarBackupReaderTest { fileMetadata); RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy( mPackageManagerStub, false /* allowApks */, fileMetadata, signatures, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE); assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME); @@ -160,7 +163,7 @@ public class TarBackupReaderTest { fileMetadata); restorePolicy = tarBackupReader.chooseRestorePolicy( mPackageManagerStub, false /* allowApks */, fileMetadata, signatures, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE); assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME); @@ -223,7 +226,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, true /* allowApks */, new FileMetadata(), null /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); verifyZeroInteractions(mBackupManagerMonitorMock); @@ -244,7 +247,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, true /* allowApks */, info, new Signature[0] /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -269,7 +272,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, true /* allowApks */, info, new Signature[0] /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -295,7 +298,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -320,7 +323,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -347,7 +350,7 @@ public class TarBackupReaderTest { RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */, - mMockPackageManagerInternal); + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -381,7 +384,8 @@ public class TarBackupReaderTest { PackageManagerStub.sPackageInfo = packageInfo; RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal); + false /* allowApks */, new FileMetadata(), signatures, + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -419,7 +423,8 @@ public class TarBackupReaderTest { doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1, packageInfo.packageName); RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal); + false /* allowApks */, new FileMetadata(), signatures, + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -456,7 +461,8 @@ public class TarBackupReaderTest { doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1, packageInfo.packageName); RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal); + false /* allowApks */, new FileMetadata(), signatures, + mMockPackageManagerInternal, mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -497,7 +503,8 @@ public class TarBackupReaderTest { doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1, packageInfo.packageName); RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - false /* allowApks */, info, signatures, mMockPackageManagerInternal); + false /* allowApks */, info, signatures, mMockPackageManagerInternal, + mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); @@ -540,7 +547,8 @@ public class TarBackupReaderTest { doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1, packageInfo.packageName); RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - true /* allowApks */, info, signatures, mMockPackageManagerInternal); + true /* allowApks */, info, signatures, mMockPackageManagerInternal, + mUserId); assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK); verifyNoMoreInteractions(mBackupManagerMonitorMock); @@ -579,7 +587,8 @@ public class TarBackupReaderTest { doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1, packageInfo.packageName); RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub, - false /* allowApks */, info, signatures, mMockPackageManagerInternal); + false /* allowApks */, info, signatures, mMockPackageManagerInternal, + mUserId); assertThat(policy).isEqualTo(RestorePolicy.IGNORE); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 38e8ac2d8f4c..535198b3dbfa 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -206,6 +206,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID; setUpUserManager(); + + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -836,6 +838,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { MockUtils.checkIntent(intent), MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID)); } + /** * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully. */ @@ -2618,6 +2621,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().lockPatternUtils .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false); dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER); + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER); verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM); @@ -4233,6 +4237,41 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isActivePasswordSufficient()); } + public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + // If there is no lock screen, the password is considered empty no matter what, because + // it provides no security. + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + mContext.packageName = admin1.getPackageName(); + setupDeviceOwner(); + + // If no password requirements are set, isActivePasswordSufficient should succeed. + assertTrue(dpm.isActivePasswordSufficient()); + + // Now set some password quality requirements. + dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + + reset(mContext.spiedContext); + final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); + PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9, + 8, 2, + 6, 1, + 0, 1); + // This should be ignored, as there is no lock screen. + dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle); + dpm.reportPasswordChanged(userHandle); + + // No broadcast should be sent. + verify(mContext.spiedContext, times(0)).sendBroadcastAsUser( + MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED), + MockUtils.checkUserHandle(userHandle)); + + // The active (nonexistent) password doesn't comply with the requirements. + assertFalse(dpm.isActivePasswordSufficient()); + } + private void setActivePasswordState(PasswordMetrics passwordMetrics) throws Exception { final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); @@ -5200,6 +5239,45 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity()); } + public void testCrossProfileCalendarPackages_initiallyEmpty() { + setAsProfileOwner(admin1); + final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + } + + public void testCrossProfileCalendarPackages_reopenDpms() { + setAsProfileOwner(admin1); + dpm.setCrossProfileCalendarPackages(admin1, null); + Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1); + assertTrue(packages == null); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertTrue(packages == null); + + dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet()); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + + final String dummyPackageName = "test"; + final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName)); + dpm.setCrossProfileCalendarPackages(admin1, testPackages); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, testPackages); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, testPackages); + } + + private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) { + assertTrue(expected != null); + assertTrue(actual != null); + assertTrue(expected.containsAll(actual)); + assertTrue(actual.containsAll(expected)); + } + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { final long ident = mServiceContext.binder.clearCallingIdentity(); mServiceContext.binder.callingUid = diff --git a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java index e0ecd3ee24f0..0b01657868a8 100644 --- a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java @@ -25,6 +25,7 @@ import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.content.ContextWrapper; +import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -45,7 +46,9 @@ import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -67,16 +70,23 @@ public class ColorDisplayServiceTest { private MockTwilightManager mTwilightManager; - private ColorDisplayController mColorDisplayController; private ColorDisplayService mColorDisplayService; + private ColorDisplayController mColorDisplayController; + private ColorDisplayService.BinderService mBinderService; + + @BeforeClass + public static void setDtm() { + final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class); + LocalServices.addService(DisplayTransformManager.class, dtm); + } @Before public void setUp() { mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - mUserId = ActivityManager.getCurrentUser(); - doReturn(mContext).when(mContext).getApplicationContext(); + mUserId = ActivityManager.getCurrentUser(); + final MockContentResolver cr = new MockContentResolver(mContext); cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); doReturn(cr).when(mContext).getContentResolver(); @@ -84,23 +94,19 @@ public class ColorDisplayServiceTest { final AlarmManager am = Mockito.mock(AlarmManager.class); doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE); - final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class); - LocalServices.addService(DisplayTransformManager.class, dtm); - mTwilightManager = new MockTwilightManager(); LocalServices.addService(TwilightManager.class, mTwilightManager); mColorDisplayController = new ColorDisplayController(mContext, mUserId); mColorDisplayService = new ColorDisplayService(mContext); + mBinderService = mColorDisplayService.new BinderService(); } @After public void tearDown() { - LocalServices.removeServiceForTest(DisplayTransformManager.class); LocalServices.removeServiceForTest(TwilightManager.class); mColorDisplayService = null; - mColorDisplayController = null; mTwilightManager = null; @@ -108,6 +114,11 @@ public class ColorDisplayServiceTest { mContext = null; } + @AfterClass + public static void removeDtm() { + LocalServices.removeServiceForTest(DisplayTransformManager.class); + } + @Test public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() { setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */); @@ -907,15 +918,14 @@ public class ColorDisplayServiceTest { } setAccessibilityColorInversion(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -926,15 +936,14 @@ public class ColorDisplayServiceTest { } setAccessibilityColorCorrection(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -946,15 +955,14 @@ public class ColorDisplayServiceTest { setAccessibilityColorCorrection(true); setAccessibilityColorInversion(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -966,12 +974,11 @@ public class ColorDisplayServiceTest { setAccessibilityColorCorrection(false); setAccessibilityColorInversion(false); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(false /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - assertActiveColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); } /** @@ -981,7 +988,7 @@ public class ColorDisplayServiceTest { * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes) */ private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) { - mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM); + mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset)); mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset)); } @@ -993,7 +1000,7 @@ public class ColorDisplayServiceTest { * @param sunriseOffset the offset relative to now for sunrise (in minutes) */ private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) { - mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT); + mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT); mTwilightManager.setTwilightState( getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset)); } @@ -1006,7 +1013,7 @@ public class ColorDisplayServiceTest { * activated (in minutes) */ private void setActivated(boolean activated, int lastActivatedTimeOffset) { - mColorDisplayController.setActivated(activated); + mBinderService.setNightDisplayActivated(activated); Secure.putStringForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(), @@ -1036,10 +1043,10 @@ public class ColorDisplayServiceTest { /** * Configures color mode via ColorDisplayController. * - * @param mode the color mode to set + * @param colorMode the color mode to set */ - private void setColorMode(int mode) { - mColorDisplayController.setColorMode(mode); + private void setColorMode(int colorMode) { + mColorDisplayController.setColorMode(colorMode); } /** @@ -1081,23 +1088,12 @@ public class ColorDisplayServiceTest { * @param activated the expected activated state of Night display */ private void assertActivated(boolean activated) { - assertWithMessage("Invalid Night display activated state") - .that(mColorDisplayController.isActivated()) + assertWithMessage("Incorrect Night display activated state") + .that(mBinderService.isNightDisplayActivated()) .isEqualTo(activated); } /** - * Convenience method for asserting that Accessibility color transform is detected. - * - * @param state {@code true} if any Accessibility transform should be activated - */ - private void assertAccessibilityTransformActivated(boolean state) { - assertWithMessage("Unexpected Accessibility color transform state") - .that(mColorDisplayController.getAccessibilityTransformActivated()) - .isEqualTo(state); - } - - /** * Convenience method for asserting that the active color mode matches expectation. * * @param mode the expected active color mode. diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java index 01199a36ff5b..0219f2201675 100644 --- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java +++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java @@ -17,15 +17,15 @@ package com.android.server.job; import android.util.KeyValueListParser; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.server.job.JobSchedulerService.MaxJobCounts; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - @RunWith(AndroidJUnit4.class) @SmallTest public class MaxJobCountsTest { @@ -43,13 +43,14 @@ public class MaxJobCountsTest { counts.parse(parser); - Assert.assertEquals(expectedTotal, counts.getTotalMax()); + Assert.assertEquals(expectedTotal, counts.getMaxTotal()); Assert.assertEquals(expectedMaxBg, counts.getMaxBg()); Assert.assertEquals(expectedMinBg, counts.getMinBg()); } @Test public void test() { + // Tests with various combinations. check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0); check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0); check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0); @@ -58,7 +59,11 @@ public class MaxJobCountsTest { check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5); check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3); check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1); + check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14); + check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15); + check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15); + // Test for overriding with a setting string. check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3); check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4); check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 2dc3510a82e5..cf89cb8f7a15 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -37,8 +37,8 @@ import android.os.FileUtils; import android.os.IProgressListener; import android.os.RemoteException; import android.os.UserManager; -import android.os.storage.StorageManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; import android.security.KeyStore; import android.test.AndroidTestCase; @@ -46,6 +46,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -85,6 +86,8 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { KeyStore mKeyStore; MockSyntheticPasswordManager mSpManager; IAuthSecret mAuthSecretService; + WindowManagerInternal mMockWindowManager; + protected boolean mHasSecureLockScreen; @Override protected void setUp() throws Exception { @@ -97,10 +100,13 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mActivityManager = mock(IActivityManager.class); mDevicePolicyManager = mock(DevicePolicyManager.class); mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); + mMockWindowManager = mock(WindowManagerInternal.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManager); mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager, mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class), @@ -114,11 +120,17 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { storageDir.mkdirs(); } + mHasSecureLockScreen = true; mLockPatternUtils = new LockPatternUtils(mContext) { @Override public ILockSettings getLockSettings() { return mService; } + + @Override + public boolean hasSecureLockScreen() { + return mHasSecureLockScreen; + } }; mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index e12f6d3be71e..5124803ee298 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -26,13 +26,12 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; import android.os.RemoteException; -import android.os.UserHandle; import android.service.gatekeeper.GateKeeperResponse; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; -import com.android.server.locksettings.LockSettingsStorage.CredentialHash; import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle; +import com.android.server.locksettings.LockSettingsStorage.CredentialHash; /** * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests @@ -54,11 +53,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { PASSWORD_QUALITY_ALPHABETIC); } + public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password", + CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); + } + public void testCreatePatternPrimaryUser() throws RemoteException { testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); } + public void testCreatePatternFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789", + CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); + } + public void testChangePasswordPrimaryUser() throws RemoteException { testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN, "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); @@ -198,6 +207,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertVerifyCredentials(userId, credential, type, -1); } + private void testCreateCredentialFailsWithoutLockScreen( + int userId, String credential, int type, int quality) throws RemoteException { + mHasSecureLockScreen = false; + + try { + mService.setLockCredential(credential, type, null, quality, userId); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + + assertFalse(mService.havePassword(userId)); + assertFalse(mService.havePattern(userId)); + } + private void testChangeCredentials(int userId, String newCredential, int newType, String oldCredential, int oldType, int quality) throws RemoteException { final long sid = 1234; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index a28a5a10e832..929c3b525db9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -27,6 +27,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -77,6 +78,7 @@ public class LockSettingsShellCommandTest { final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); mCommand = new LockSettingsShellCommand(mLockPatternUtils); + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } @Test @@ -103,6 +105,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePin_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pin", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePassword() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true); @@ -115,6 +127,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePassword_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-password", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePattern() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); @@ -126,6 +148,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePattern_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pattern", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testClear() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 94e02bc4d35f..0595a5b2e9a0 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -40,10 +40,10 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; -import java.util.ArrayList; - import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + /** * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests @@ -448,6 +448,37 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); } + public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception { + final String password = "password"; + final String pattern = "123654"; + final String token = "some-high-entropy-secure-token"; + + mHasSecureLockScreen = false; + enableSyntheticPassword(); + long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(password, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePassword(PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(pattern, + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePattern(PRIMARY_USER_ID)); + } + public void testgetHashFactorPrimaryUser() throws RemoteException { final String password = "password"; mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index bc1f7981258d..6845f15f6a28 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -43,6 +43,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.IUidObserver; import android.app.Person; +import android.app.admin.DevicePolicyManager; import android.app.usage.UsageStatsManagerInternal; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; @@ -143,8 +144,10 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { switch (name) { case Context.USER_SERVICE: return mMockUserManager; + case Context.DEVICE_POLICY_SERVICE: + return mMockDevicePolicyManager; } - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Couldn't find system service: " + name); } @Override @@ -610,6 +613,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected PackageManager mMockPackageManager; protected PackageManagerInternal mMockPackageManagerInternal; protected UserManager mMockUserManager; + protected DevicePolicyManager mMockDevicePolicyManager; protected UserManagerInternal mMockUserManagerInternal; protected UsageStatsManagerInternal mMockUsageStatsManagerInternal; protected ActivityManagerInternal mMockActivityManagerInternal; @@ -750,6 +754,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { mMockPackageManager = mock(PackageManager.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockUserManager = mock(UserManager.class); + mMockDevicePolicyManager = mock(DevicePolicyManager.class); mMockUserManagerInternal = mock(UserManagerInternal.class); mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class); mMockActivityManagerInternal = mock(ActivityManagerInternal.class); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 73e96134167a..742ae41ed9f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -173,7 +173,8 @@ public class PackageInstallerSessionTest { /* isReady */ staged ? true : false, /* isFailed */ false, /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED); + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED, + /* stagedSessionErrorMessage */ "some error"); } private void dumpSession(PackageInstallerSession session) { @@ -295,6 +296,8 @@ public class PackageInstallerSessionTest { assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); + assertEquals(expected.getStagedSessionErrorMessage(), + actual.getStagedSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isSealed(), actual.isSealed()); assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java index f817e8e33b31..6da202b93065 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java @@ -16,8 +16,6 @@ package com.android.server.pm.dex; -import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.atMost; @@ -26,10 +24,12 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.os.UserHandle; import android.os.storage.StorageManager; import androidx.test.filters.SmallTest; @@ -56,40 +56,44 @@ import org.mockito.stubbing.Stubber; public class DexLoggerTests { private static final String OWNING_PACKAGE_NAME = "package.name"; private static final String VOLUME_UUID = "volUuid"; - private static final String DEX_PATH = "/bar/foo.jar"; + private static final String FILE_PATH = "/bar/foo.jar"; private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE; private static final int OWNER_UID = 43; private static final int OWNER_USER_ID = 44; // Obtained via: echo -n "foo.jar" | sha256sum - private static final String DEX_FILENAME_HASH = + private static final String FILENAME_HASH = "91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F"; - private static final byte[] CONTENT_HASH_BYTES = new byte[] { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + private static final byte[] CONTENT_HASH_BYTES = new byte[]{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; private static final String CONTENT_HASH = "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"; private static final byte[] EMPTY_BYTES = {}; - @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH = + "dcl:" + FILENAME_HASH; + private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = + EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH; + private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH = + "dcln:" + FILENAME_HASH + " " + CONTENT_HASH; + + @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT); @Mock IPackageManager mPM; @Mock Installer mInstaller; - private PackageDynamicCodeLoading mPackageDynamicCodeLoading; private DexLogger mDexLogger; private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create(); private boolean mWriteTriggered = false; - private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = - DEX_FILENAME_HASH + " " + CONTENT_HASH; @Before public void setup() throws Exception { // Disable actually attempting to do file writes. - mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() { + PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() { @Override void maybeWriteAsync() { mWriteTriggered = true; @@ -102,13 +106,13 @@ public class DexLoggerTests { }; // For test purposes capture log messages as well as sending to the event log. - mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) { + mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) { @Override - void writeDclEvent(int uid, String message) { - super.writeDclEvent(uid, message); - mMessagesForUid.put(uid, message); - } - }; + void writeDclEvent(String subtag, int uid, String message) { + super.writeDclEvent(subtag, uid, message); + mMessagesForUid.put(uid, subtag + ":" + message); + } + }; // Make the owning package exist in our mock PackageManager. ApplicationInfo appInfo = new ApplicationInfo(); @@ -124,9 +128,9 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_withFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); @@ -139,13 +143,13 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_noFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -154,13 +158,14 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_hashingFails() throws Exception { - whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test"))); + whenFileIsHashed(FILE_PATH, + doThrow(new InstallerException("Intentional failure for test"))); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -178,11 +183,23 @@ public class DexLoggerTests { } @Test + public void testOneLoader_pathTraversal() throws Exception { + String filePath = "/bar/../secret/foo.jar"; + whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES)); + setPackageUid(OWNING_PACKAGE_NAME, -1); + + recordLoad(OWNING_PACKAGE_NAME, filePath); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid).isEmpty(); + } + + @Test public void testOneLoader_differentOwner() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", 1001); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001); @@ -192,10 +209,10 @@ public class DexLoggerTests { @Test public void testOneLoader_differentOwner_uninstalled() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", -1); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -203,22 +220,38 @@ public class DexLoggerTests { } @Test + public void testNativeCodeLoad() throws Exception { + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); + + recordLoadNative(FILE_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid) + .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH); + + assertThat(mWriteTriggered).isFalse(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()) + .containsExactly(OWNING_PACKAGE_NAME); + } + + @Test public void testMultipleLoadersAndFiles() throws Exception { String otherDexPath = "/bar/nosuchdir/foo.jar"; - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES)); setPackageUid("other.package.name1", 1001); setPackageUid("other.package.name2", 1002); - recordLoad("other.package.name1", DEX_PATH); + recordLoad("other.package.name1", FILE_PATH); recordLoad("other.package.name1", otherDexPath); - recordLoad("other.package.name2", DEX_PATH); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad("other.package.name2", FILE_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID); assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH); - assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH); @@ -233,7 +266,7 @@ public class DexLoggerTests { @Test public void testUnknownOwner() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading("other.package.name"); assertThat(mMessagesForUid).isEmpty(); @@ -244,7 +277,7 @@ public class DexLoggerTests { @Test public void testUninstalledPackage() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -262,7 +295,16 @@ public class DexLoggerTests { } private void recordLoad(String loadingPackageName, String dexPath) { - mPackageDynamicCodeLoading.record( - OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName); + mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName); + mWriteTriggered = false; + } + + private void recordLoadNative(String nativePath) throws Exception { + int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID); + String[] packageNames = { OWNING_PACKAGE_NAME }; + when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames); + + mDexLogger.recordNative(loadingUid, nativePath); + mWriteTriggered = false; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 7cd8ceddfd23..2ddc71f570b8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -55,6 +55,7 @@ import org.mockito.quality.Strictness; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -685,4 +686,68 @@ public class DexManagerTests { return mPackageInfo.applicationInfo.splitSourceDirs[length - 1]; } } + + private boolean shouldPackageRunOob( + boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled, + String overrideWhitelist, Collection<String> packageNamesInSameProcess) { + return DexManager.isPackageSelectedToRunOobInternal( + isDefaultEnabled, defaultWhitelist, overrideEnabled, overrideWhitelist, + packageNamesInSameProcess); + } + + @Test + public void testOobPackageSelectionSwitch() { + // Feature is off by default, not overriden + assertFalse(shouldPackageRunOob(false, "ALL", null, null, null)); + + // Feature is off by default, overriden + assertTrue(shouldPackageRunOob(false, "ALL", "true", "ALL", null)); + assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null)); + assertFalse(shouldPackageRunOob(false, "ALL", "false", "ALL", null)); + assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null)); + + // Feature is on by default, not overriden + assertTrue(shouldPackageRunOob(true, "ALL", null, null, null)); + assertTrue(shouldPackageRunOob(true, "ALL", null, null, null)); + assertTrue(shouldPackageRunOob(true, "ALL", null, "ALL", null)); + + // Feature is on by default, overriden + assertTrue(shouldPackageRunOob(true, "ALL", "true", null, null)); + assertTrue(shouldPackageRunOob(true, "ALL", "true", "ALL", null)); + assertFalse(shouldPackageRunOob(true, "ALL", "false", null, null)); + assertFalse(shouldPackageRunOob(true, "ALL", "false", "ALL", null)); + } + + @Test + public void testOobPackageSelectionWhitelist() { + // Various whitelist of apps to run in OOB mode. + final String kWhitelistApp0 = "com.priv.app0"; + final String kWhitelistApp1 = "com.priv.app1"; + final String kWhitelistApp2 = "com.priv.app2"; + final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2"; + + // Packages that shares the targeting process. + final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2"); + + // Feature is off, whitelist does not matter + assertFalse(shouldPackageRunOob(false, kWhitelistApp0, null, null, runningPackages)); + assertFalse(shouldPackageRunOob(false, kWhitelistApp1, null, null, runningPackages)); + assertFalse(shouldPackageRunOob(false, "", null, kWhitelistApp1, runningPackages)); + assertFalse(shouldPackageRunOob(false, "", null, "ALL", runningPackages)); + assertFalse(shouldPackageRunOob(false, "ALL", null, "ALL", runningPackages)); + assertFalse(shouldPackageRunOob(false, "ALL", null, "", runningPackages)); + + // Feature is on, app not in default or overridden whitelist + assertFalse(shouldPackageRunOob(true, kWhitelistApp0, null, null, runningPackages)); + assertFalse(shouldPackageRunOob(true, "", null, kWhitelistApp0, runningPackages)); + assertFalse(shouldPackageRunOob(true, "ALL", null, kWhitelistApp0, runningPackages)); + + // Feature is on, app in default or overridden whitelist + assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, null, runningPackages)); + assertTrue(shouldPackageRunOob(true, kWhitelistApp2, null, null, runningPackages)); + assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, null, null, runningPackages)); + assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, "ALL", runningPackages)); + assertTrue(shouldPackageRunOob(true, "", null, kWhitelistApp1, runningPackages)); + assertTrue(shouldPackageRunOob(true, "ALL", null, kWhitelistApp1, runningPackages)); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java new file mode 100644 index 000000000000..9f1cbcd7ec27 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.attention.AttentionManagerInternal; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class AttentionDetectorTest extends AndroidTestCase { + + private @Mock AttentionManagerInternal mAttentionManagerInternal; + private @Mock Runnable mOnUserAttention; + private TestableAttentionDetector mAttentionDetector; + private long mAttentionTimeout; + private long mNextDimming; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any())) + .thenReturn(true); + mAttentionDetector = new TestableAttentionDetector(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE); + mAttentionDetector.setAttentionServiceSupported(true); + mNextDimming = SystemClock.uptimeMillis() + 3000L; + } + + @Test + public void testOnUserActivity_checksAttention() { + long when = registerAttention(); + verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnUserActivity_doesntCheckIfNotSupported() { + mAttentionDetector.setAttentionServiceSupported(false); + long when = registerAttention(); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(mNextDimming).isEqualTo(when); + } + + @Test + public void onUserActivity_ignoresWhiteListedActivityTypes() { + for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) { + int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i); + if (result == -1) { + throw new AssertionError("User activity " + i + " isn't listed in" + + " AttentionDetector#onUserActivity. Please consider how this new activity" + + " type affects the attention service."); + } + } + } + + @Test + public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() { + long now = SystemClock.uptimeMillis(); + mNextDimming = now; + mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(mNextDimming + 5000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_ignoresAfterMaximumExtension() { + long now = SystemClock.uptimeMillis(); + mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(now + 2000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_skipsIfAlreadyScheduled() { + registerAttention(); + reset(mAttentionManagerInternal); + long when = mAttentionDetector.updateUserActivity(mNextDimming); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() { + registerAttention(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP); + verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt()); + } + + @Test + public void testCallbackOnSuccess_ignoresIfNoAttention() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + @Test + public void testCallbackOnSuccess_callsCallback() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention).run(); + } + + @Test + public void testCallbackOnFailure_unregistersCurrentRequestCode() { + registerAttention(); + mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_FAILURE_UNKNOWN); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + private long registerAttention() { + mAttentionTimeout = 4000L; + mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH); + return mAttentionDetector.updateUserActivity(mNextDimming); + } + + private class TestableAttentionDetector extends AttentionDetector { + + private boolean mAttentionServiceSupported; + + TestableAttentionDetector() { + super(AttentionDetectorTest.this.mOnUserAttention, new Object()); + mAttentionManager = mAttentionManagerInternal; + mMaximumExtensionMillis = 10000L; + } + + void setAttentionServiceSupported(boolean supported) { + mAttentionServiceSupported = supported; + } + + @Override + public boolean isAttentionServiceSupported() { + return mAttentionServiceSupported; + } + + @Override + public long getAttentionTimeout() { + return mAttentionTimeout; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java index b348aeef802e..5d69bbdcf0c7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java @@ -18,12 +18,15 @@ package com.android.server.usage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PendingIntent; +import android.app.usage.UsageStatsManagerInternal; import android.os.HandlerThread; import android.os.Looper; +import android.os.UserHandle; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -64,7 +67,7 @@ public class AppTimeLimitControllerTests { private static final long TIME_30_MIN = 30 * 60_000L; private static final long TIME_10_MIN = 10 * 60_000L; - private static final long TIME_1_MIN = 10 * 60_000L; + private static final long TIME_1_MIN = 1 * 60_000L; private static final long MAX_OBSERVER_PER_UID = 10; private static final long MIN_TIME_LIMIT = 4_000L; @@ -128,6 +131,11 @@ public class AppTimeLimitControllerTests { } @Override + protected long getAppUsageLimitObserverPerUidLimit() { + return MAX_OBSERVER_PER_UID; + } + + @Override protected long getMinTimeLimit() { return MIN_TIME_LIMIT; } @@ -164,6 +172,16 @@ public class AppTimeLimitControllerTests { assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2)); } + /** Verify app usage limit observer is added */ + @Test + public void testAppUsageLimitObserver_AddObserver() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1)); + addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2)); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** Verify app usage observer is removed */ @Test public void testAppUsageObserver_RemoveObserver() { @@ -182,6 +200,15 @@ public class AppTimeLimitControllerTests { assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1)); } + /** Verify app usage limit observer is removed */ + @Test + public void testAppUsageLimitObserver_RemoveObserver() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1)); + mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** Verify nothing happens when a nonexistent app usage observer is removed */ @Test public void testAppUsageObserver_RemoveMissingObserver() { @@ -218,6 +245,24 @@ public class AppTimeLimitControllerTests { assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1)); } + /** Verify nothing happens when a nonexistent app usage limit observer is removed */ + @Test + public void testAppUsageLimitObserver_RemoveMissingObserver() { + assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1)); + try { + mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID); + } catch (Exception e) { + StringWriter sw = new StringWriter(); + sw.write("Hit exception trying to remove nonexistent observer:\n"); + sw.write(e.toString()); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + sw.write("\nTest Failed!"); + fail(sw.toString()); + } + assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** Re-adding an observer should result in only one copy */ @Test public void testAppUsageObserver_ObserverReAdd() { @@ -242,22 +287,39 @@ public class AppTimeLimitControllerTests { assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1)); } + /** Re-adding an observer should result in only one copy */ + @Test + public void testAppUsageLimitObserver_ObserverReAdd() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1)); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN); + assertTrue("Observer wasn't added", + getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN); + mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** Different type observers can be registered to the same observerId value */ @Test public void testAllObservers_ExclusiveObserverIds() { addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN); addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN); assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1)); AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID, OBS_ID1); AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID, OBS_ID1); + AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver( + UID, OBS_ID1); // Verify data still intact assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs()); assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs()); + assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs()); } /** Verify that usage across different apps within a group are added up */ @@ -299,7 +361,7 @@ public class AppTimeLimitControllerTests { @Test public void testUsageSessionObserver_Accumulation() throws Exception { setTime(0L); - addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN); startUsage(PKG_SOC1); // Add 10 mins setTime(TIME_10_MIN); @@ -330,6 +392,41 @@ public class AppTimeLimitControllerTests { assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); } + /** Verify that usage across different apps within a group are added up */ + @Test + public void testAppUsageLimitObserver_Accumulation() throws Exception { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + startUsage(PKG_SOC1); + // Add 10 mins + setTime(TIME_10_MIN); + stopUsage(PKG_SOC1); + + AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + + long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); + assertEquals(TIME_10_MIN * 2, timeRemaining); + + startUsage(PKG_SOC1); + setTime(TIME_10_MIN * 2); + stopUsage(PKG_SOC1); + + timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); + assertEquals(TIME_10_MIN, timeRemaining); + + setTime(TIME_30_MIN); + + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + + // Add a different package in the group + startUsage(PKG_GAME1); + setTime(TIME_30_MIN + TIME_10_MIN); + stopUsage(PKG_GAME1); + + assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + /** Verify that time limit does not get triggered due to a different app */ @Test public void testAppUsageObserver_TimeoutOtherApp() throws Exception { @@ -355,6 +452,18 @@ public class AppTimeLimitControllerTests { } + /** Verify that time limit does not get triggered due to a different app */ + @Test + public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L); + startUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + setTime(6_000L); + stopUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + /** Verify the timeout message is delivered at the right time */ @Test public void testAppUsageObserver_Timeout() throws Exception { @@ -385,6 +494,19 @@ public class AppTimeLimitControllerTests { assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); } + /** Verify the timeout message is delivered at the right time */ + @Test + public void testAppUsageLimitObserver_Timeout() throws Exception { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L); + startUsage(PKG_SOC1); + setTime(6_000L); + assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Verify that the observer was not removed + assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** If an app was already running, make sure it is partially counted towards the time limit */ @Test public void testAppUsageObserver_AlreadyRunning() throws Exception { @@ -423,6 +545,25 @@ public class AppTimeLimitControllerTests { assertTrue(hasUsageSessionObserver(UID, OBS_ID2)); } + /** If an app was already running, make sure it is partially counted towards the time limit */ + @Test + public void testAppUsageLimitObserver_AlreadyRunning() throws Exception { + setTime(TIME_10_MIN); + startUsage(PKG_GAME1); + setTime(TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); + setTime(TIME_30_MIN + TIME_10_MIN); + stopUsage(PKG_GAME1); + assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + + startUsage(PKG_GAME2); + setTime(TIME_30_MIN + TIME_30_MIN); + stopUsage(PKG_GAME2); + assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + // Verify that the observer was not removed + assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2)); + } + /** If watched app is already running, verify the timeout callback happens at the right time */ @Test public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception { @@ -464,6 +605,24 @@ public class AppTimeLimitControllerTests { assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); } + /** If watched app is already running, verify the timeout callback happens at the right time */ + @Test + public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception { + setTime(0); + startUsage(PKG_SOC1); + setTime(TIME_10_MIN); + // 10 second time limit + addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L); + setTime(TIME_10_MIN + 5_000L); + // Shouldn't call back in 6 seconds + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + setTime(TIME_10_MIN + 10_000L); + // Should call back by 11 seconds (6 earlier + 5 now) + assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS)); + // Verify that the observer was not removed + assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1)); + } + /** * Verify that App Time Limit Controller will limit the number of observerIds for app usage * observers @@ -525,6 +684,37 @@ public class AppTimeLimitControllerTests { assertTrue("Should have caused an IllegalStateException", receivedException); } + /** + * Verify that App Time Limit Controller will limit the number of observerIds for app usage + * limit observers + */ + @Test + public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception { + boolean receivedException = false; + int ANOTHER_UID = UID + 1; + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN); + // Readding an observer should not cause an IllegalStateException + addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN); + // Adding an observer for a different uid shouldn't cause an IllegalStateException + mController.addAppUsageLimitObserver( + ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID); + try { + addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN); + } catch (IllegalStateException ise) { + receivedException = true; + } + assertTrue("Should have caused an IllegalStateException", receivedException); + } + /** Verify that addAppUsageObserver minimum time limit is one minute */ @Test public void testAppUsageObserver_MinimumTimeLimit() throws Exception { @@ -553,6 +743,20 @@ public class AppTimeLimitControllerTests { assertTrue("Should have caused an IllegalArgumentException", receivedException); } + /** Verify that addAppUsageLimitObserver minimum time limit is one minute */ + @Test + public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception { + boolean receivedException = false; + // adding an observer with a one minute time limit should not cause an exception + addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT); + try { + addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1); + } catch (IllegalArgumentException iae) { + receivedException = true; + } + assertTrue("Should have caused an IllegalArgumentException", receivedException); + } + /** Verify that concurrent usage from multiple apps in the same group will counted correctly */ @Test public void testAppUsageObserver_ConcurrentUsage() throws Exception { @@ -599,6 +803,29 @@ public class AppTimeLimitControllerTests { assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); } + /** Verify that concurrent usage from multiple apps in the same group will counted correctly */ + @Test + public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + startUsage(PKG_SOC1); + // Add 10 mins + setTime(TIME_10_MIN); + + // Add a different package in the group will first package is still in use + startUsage(PKG_GAME1); + setTime(TIME_10_MIN * 2); + // Stop first package usage + stopUsage(PKG_SOC1); + + setTime(TIME_30_MIN); + stopUsage(PKG_GAME1); + + assertEquals(TIME_30_MIN, group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + /** Verify that a session will continue if usage starts again within the session threshold */ @Test public void testUsageSessionObserver_ContinueSession() throws Exception { @@ -737,6 +964,97 @@ public class AppTimeLimitControllerTests { assertFalse(hasAppUsageObserver(UID, OBS_ID1)); } + /** Verify app usage limit observer added correctly reports it being a group limit */ + @Test + public void testAppUsageLimitObserver_IsGroupLimit() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + assertNotNull("Observer wasn't added", group); + assertTrue("Observer didn't correctly report being a group limit", + group.isGroupLimit()); + } + + /** Verify app usage limit observer added correctly reports it being not a group limit */ + @Test + public void testAppUsageLimitObserver_IsNotGroupLimit() { + addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN); + AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + assertNotNull("Observer wasn't added", group); + assertFalse("Observer didn't correctly report not being a group limit", + group.isGroupLimit()); + } + + /** Verify app usage limit observer added correctly reports its total usage limit */ + @Test + public void testAppUsageLimitObserver_GetTotalUsageLimit() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + assertNotNull("Observer wasn't added", group); + assertEquals("Observer didn't correctly report total usage limit", + TIME_30_MIN, group.getTotaUsageLimit()); + } + + /** Verify app usage limit observer added correctly reports its total usage limit */ + @Test + public void testAppUsageLimitObserver_GetUsageRemaining() { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + startUsage(PKG_SOC1); + setTime(TIME_10_MIN); + stopUsage(PKG_SOC1); + AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); + assertNotNull("Observer wasn't added", group); + assertEquals("Observer didn't correctly report total usage limit", + TIME_10_MIN * 2, group.getUsageRemaining()); + } + + /** Verify the app usage limit observer with the smallest usage limit remaining is returned + * when querying the getAppUsageLimit API. + */ + @Test + public void testAppUsageLimitObserver_GetAppUsageLimit() { + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN); + UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1); + assertEquals("Observer with the smallest usage limit remaining wasn't returned", + TIME_10_MIN, group.getTotalUsageLimit()); + } + + /** Verify the app usage limit observer with the smallest usage limit remaining is returned + * when querying the getAppUsageLimit API. + */ + @Test + public void testAppUsageLimitObserver_GetAppUsageLimitUsed() { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN); + startUsage(PKG_GAME1); + setTime(TIME_10_MIN * 2 + TIME_1_MIN); + stopUsage(PKG_GAME1); + // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is + // in both groups, GROUP1 should be returned since it has a smaller time remaining + UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1); + assertEquals("Observer with the smallest usage limit remaining wasn't returned", + TIME_1_MIN * 9, group.getUsageRemaining()); + } + + /** Verify the app usage limit observer with the smallest usage limit remaining is returned + * when querying the getAppUsageLimit API. + */ + @Test + public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() { + setTime(0L); + addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); + addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN); + startUsage(PKG_SOC1); + setTime(TIME_10_MIN); + stopUsage(PKG_SOC1); + // GROUP_SOC should be returned since it should be completely used up (0ms remaining) + UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1); + assertEquals("Observer with the smallest usage limit remaining wasn't returned", + 0L, group.getUsageRemaining()); + } + private void startUsage(String packageName) { mController.noteUsageStart(packageName, USER_ID); } @@ -759,6 +1077,10 @@ public class AppTimeLimitControllerTests { null, null, USER_ID); } + private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) { + mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID); + } + /** Is there still an app usage observer by that id */ private boolean hasAppUsageObserver(int uid, int observerId) { return mController.getAppUsageGroup(uid, observerId) != null; @@ -769,6 +1091,20 @@ public class AppTimeLimitControllerTests { return mController.getSessionUsageGroup(uid, observerId) != null; } + /** Is there still an app usage limit observer by that id */ + private boolean hasAppUsageLimitObserver(int uid, int observerId) { + return mController.getAppUsageLimitGroup(uid, observerId) != null; + } + + private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver( + int uid, int observerId) { + return mController.getAppUsageLimitGroup(uid, observerId); + } + + private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) { + return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID)); + } + private void setTime(long time) { mUptimeMillis = time; } diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index 860656bf47f4..8d9b3cfcff35 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -45,6 +45,8 @@ import java.util.Locale; @RunWith(AndroidJUnit4.class) @SmallTest public class UsageStatsDatabaseTest { + + private static final int MAX_TESTED_VERSION = 4; protected Context mContext; private UsageStatsDatabase mUsageStatsDatabase; private File mTestDir; @@ -131,8 +133,8 @@ public class UsageStatsDatabaseTest { for (int i = 0; i < numberOfEvents; i++) { Event event = new Event(); - final int packageInt = ((i / 3) % 7); - event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps" + final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps" + event.mPackage = "fake.package.name" + packageInt; if (packageInt == 3) { // Third app is an instant app event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; @@ -144,6 +146,13 @@ public class UsageStatsDatabaseTest { event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type event.mInstanceId = instanceId; + + final int rootPackageInt = (i % 5); // 5 "apps" start each task + event.mTaskRootPackage = "fake.package.name" + rootPackageInt; + + final int rootClassInt = i % 6; + event.mTaskRootClass = ".fake.class.name" + rootClassInt; + switch (event.mEventType) { case Event.CONFIGURATION_CHANGE: //empty config, @@ -163,7 +172,7 @@ public class UsageStatsDatabaseTest { break; } - mIntervalStats.events.insert(event); + mIntervalStats.addEvent(event); mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType, event.mInstanceId); @@ -234,31 +243,40 @@ public class UsageStatsDatabaseTest { assertEquals(us1.mChooserCounts, us2.mChooserCounts); } - void compareUsageEvent(Event e1, Event e2, int debugId) { - assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId); - assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId); - assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId); - assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId); - switch (e1.mEventType) { - case Event.CONFIGURATION_CHANGE: - assertEquals(e1.mConfiguration, e2.mConfiguration, - "Usage event " + debugId + e2.mConfiguration.toString()); - break; - case Event.SHORTCUT_INVOCATION: - assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId); - break; - case Event.STANDBY_BUCKET_CHANGED: - assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId); - break; - case Event.NOTIFICATION_INTERRUPTION: - assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId, - "Usage event " + debugId); - break; + void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) { + switch (minVersion) { + case 4: // test fields added in version 4 + assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId); + assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId); + assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId); + // fallthrough + default: + assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId); + assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId); + assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId); + assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId); + switch (e1.mEventType) { + case Event.CONFIGURATION_CHANGE: + assertEquals(e1.mConfiguration, e2.mConfiguration, + "Usage event " + debugId + e2.mConfiguration.toString()); + break; + case Event.SHORTCUT_INVOCATION: + assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId); + break; + case Event.STANDBY_BUCKET_CHANGED: + assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, + "Usage event " + debugId); + break; + case Event.NOTIFICATION_INTERRUPTION: + assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId, + "Usage event " + debugId); + break; + } + assertEquals(e1.mFlags, e2.mFlags); } - assertEquals(e1.mFlags, e2.mFlags); } - void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) { + void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) { assertEquals(stats1.majorVersion, stats2.majorVersion); assertEquals(stats1.minorVersion, stats2.minorVersion); assertEquals(stats1.beginTime, stats2.beginTime); @@ -311,7 +329,7 @@ public class UsageStatsDatabaseTest { } else { assertEquals(stats1.events.size(), stats2.events.size()); for (int i = 0; i < stats1.events.size(); i++) { - compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i); + compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion); } } } @@ -326,7 +344,7 @@ public class UsageStatsDatabaseTest { mIntervalStatsVerifier); assertEquals(1, stats.size()); - compareIntervalStats(mIntervalStats, stats.get(0)); + compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION); } /** @@ -359,8 +377,10 @@ public class UsageStatsDatabaseTest { mIntervalStatsVerifier); assertEquals(1, stats.size()); + + final int minVersion = oldVersion < newVersion ? oldVersion : newVersion; // The written and read IntervalStats should match - compareIntervalStats(mIntervalStats, stats.get(0)); + compareIntervalStats(mIntervalStats, stats.get(0), minVersion); } /** @@ -401,7 +421,7 @@ public class UsageStatsDatabaseTest { if (mIntervalStats.events != null) mIntervalStats.events.clear(); // The written and read IntervalStats should match - compareIntervalStats(mIntervalStats, stats.get(0)); + compareIntervalStats(mIntervalStats, stats.get(0), version); } /** diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c0f9b80b2e08..9c6ab0ab9aa9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -245,8 +245,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Override - void logSmartSuggestionsVisible(NotificationRecord r) { - super.logSmartSuggestionsVisible(r); + void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) { + super.logSmartSuggestionsVisible(r, notificationLocation); countLogSmartSuggestionsVisible++; } @@ -3410,6 +3410,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); + assertEquals(1, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(1, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + } + + @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture()); + assertEquals(2, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(2, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName()); + } + + @Test + public void testNoNotificationsHiddenOnDistractingPackageBroadcast() { + // post notification from this package + final NotificationRecord notif1 = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + mService.addNotification(notif1); + + // on broadcast, nothing is hidden since no notifications are of package "test_package" + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"}); + ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); + assertEquals(0, captor.getValue().size()); + } + + @Test public void testCanUseManagedServicesLowRamNoWatchNullPkg() { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mActivityManager.isLowRamDevice()).thenReturn(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index a381023590c3..056568a0de64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -549,8 +549,7 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(), eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(), eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(), - eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(), - any(), eq(false)); + any(), anyInt(), anyBoolean(), any(), eq(false)); } /** @@ -599,6 +598,10 @@ public class ActivityStarterTests extends ActivityTestsBase { Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1, UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, false, false, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false, + Process.NFC_UID, false, PROCESS_STATE_TOP + 1, + UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, + false, false, false); runAndVerifyBackgroundActivityStartsSubtest( "disallowed_callingUidHasVisibleWindow_notAborted", false, UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java new file mode 100644 index 000000000000..19ace3c0336c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.os.IBinder; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationDefinition; +import android.view.RemoteAnimationTarget; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for change transitions + * + * Build/Install/Run: + * atest WmTests:AppChangeTransitionTests + */ +@FlakyTest(detail = "Promote when shown to be stable.") +@SmallTest +public class AppChangeTransitionTests extends WindowTestsBase { + + private TaskStack mStack; + private Task mTask; + private WindowTestUtils.TestAppWindowToken mToken; + + @Before + public void setUp() throws Exception { + mStack = createTaskStackOnDisplay(mDisplayContent); + mTask = createTaskInStack(mStack, 0 /* userId */); + mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + mToken.mSkipOnParentSet = false; + + mTask.addChild(mToken, 0); + } + + class TestRemoteAnimationRunner implements IRemoteAnimationRunner { + @Override + public void onAnimationStart(RemoteAnimationTarget[] apps, + IRemoteAnimationFinishedCallback finishedCallback) { + for (RemoteAnimationTarget target : apps) { + assertNotNull(target.startBounds); + } + try { + finishedCallback.onAnimationFinished(); + } catch (Exception e) { + throw new RuntimeException("Something went wrong"); + } + } + + @Override + public void onAnimationCancelled() { + } + + @Override + public IBinder asBinder() { + return null; + } + } + + @Test + public void testModeChangeRemoteAnimatorNoSnapshot() { + RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); + RemoteAnimationAdapter adapter = + new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); + definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); + mDisplayContent.registerRemoteAnimations(definition); + + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(1, mDisplayContent.mChangingApps.size()); + assertNull(mToken.getThumbnail()); + + waitUntilHandlersIdle(); + mToken.removeImmediately(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 1c5391ed3a6c..9ce579512eda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -106,7 +106,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { } public void notifyTransitionStarting(int transit) { - mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0); + mListener.onAppTransitionStartingLocked(transit, 0, 0, 0); } public void notifyTransitionFinished() { diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 374078625f4a..a498a1a9172a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -94,9 +94,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); topBar.getFrameLw().set(0, 0, 500, 100); mProvider.setWindow(topBar, null); - mProvider.updateControlForTarget(target); + mProvider.updateControlForTarget(target, false /* force */); assertNotNull(mProvider.getControl()); - mProvider.updateControlForTarget(null); + mProvider.updateControlForTarget(null, false /* force */); assertNull(mProvider.getControl()); } @@ -106,7 +106,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); topBar.getFrameLw().set(0, 0, 500, 100); mProvider.setWindow(topBar, null); - mProvider.updateControlForTarget(target); + mProvider.updateControlForTarget(target, false /* force */); InsetsState state = new InsetsState(); state.getSource(TYPE_TOP_BAR).setVisible(false); mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 413b6f4b3905..b867799c16cb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.eq; import android.graphics.Point; @@ -43,6 +44,7 @@ import androidx.test.filters.SmallTest; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; +import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.Before; @@ -60,8 +62,10 @@ import org.mockito.MockitoAnnotations; public class RemoteAnimationControllerTest extends WindowTestsBase { @Mock SurfaceControl mMockLeash; + @Mock SurfaceControl mMockThumbnailLeash; @Mock Transaction mMockTransaction; @Mock OnAnimationFinishedCallback mFinishedCallback; + @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback; @Mock IRemoteAnimationRunner mMockRunner; private RemoteAnimationAdapter mAdapter; private RemoteAnimationController mController; @@ -73,7 +77,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { MockitoAnnotations.initMocks(this); when(mMockRunner.asBinder()).thenReturn(new Binder()); - mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); + mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */); mAdapter.setCallingPid(123); mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0); mController = new RemoteAnimationController(mWm, mAdapter, mHandler); @@ -84,8 +88,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mOpeningApps.add(win.mAppToken); try { - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); @@ -117,8 +121,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testCancel() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -129,8 +133,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testTimeout() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -147,8 +151,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mWm.setAnimationScale(2, 5.0f); final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -176,8 +180,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testNotReallyStarted() { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null); mController.goodToGo(); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -186,10 +190,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { public void testOneNotStarted() throws Exception { final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); - mController.createAnimationAdapter(win1.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + mController.createRemoteAnimationRecord(win1.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); @@ -205,8 +209,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testRemovedBeforeStarted() { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); win.mAppToken.removeImmediately(); mController.goodToGo(); @@ -214,6 +218,49 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mFinishedCallback).onAnimationFinished(eq(adapter)); } + @Test + public void testChange() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mChangingApps.add(win.mAppToken); + try { + final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( + win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), + new Rect(0, 0, 200, 200)); + assertNotNull(record.mThumbnailAdapter); + ((AnimationAdapter) record.mAdapter) + .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, + mMockTransaction, mThumbnailFinishedCallback); + mController.goodToGo(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); + assertEquals(new Point(50, 100), app.position); + assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); + assertEquals(new Rect(0, 0, 200, 200), app.startBounds); + assertEquals(mMockLeash, app.leash); + assertEquals(mMockThumbnailLeash, app.startLeash); + assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); + verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); + verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 200, 200)); + verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter)); + verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter)); + } finally { + mDisplayContent.mChangingApps.clear(); + } + } + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { verify(binder, atLeast(0)).asBinder(); verifyNoMoreInteractions(binder); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java new file mode 100644 index 000000000000..a7c84a1c28b4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Display.INVALID_DISPLAY; + +import static com.android.server.wm.ActivityDisplay.POSITION_TOP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.content.pm.ApplicationInfo; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; + +/** + * Tests for the {@link WindowProcessController} class. + * + * Build/Install/Run: + * atest WmTests:WindowProcessControllerTests + */ +@Presubmit +public class WindowProcessControllerTests extends ActivityTestsBase { + + @Test + public void testDisplayConfigurationListener() { + final WindowProcessController wpc = new WindowProcessController( + mService, mock(ApplicationInfo.class), null, 0, -1, null, null); + //By default, the process should not listen to any display. + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + + // Register to display 1 as a listener. + TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer(); + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1); + assertTrue(testActivityDisplay1.containsListener(wpc)); + assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId()); + + // Move to display 2. + TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer(); + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2); + assertFalse(testActivityDisplay1.containsListener(wpc)); + assertTrue(testActivityDisplay2.containsListener(wpc)); + assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId()); + + // Null ActivityDisplay will not change anything. + wpc.registerDisplayConfigurationListenerLocked(null); + assertTrue(testActivityDisplay2.containsListener(wpc)); + assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId()); + + // Unregister listener will remove the wpc from registered displays. + wpc.unregisterDisplayConfigurationListenerLocked(); + assertFalse(testActivityDisplay1.containsListener(wpc)); + assertFalse(testActivityDisplay2.containsListener(wpc)); + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + + // Unregistration still work even if the display was removed. + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1); + assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId()); + mRootActivityContainer.removeChild(testActivityDisplay1); + wpc.unregisterDisplayConfigurationListenerLocked(); + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + } + + private TestActivityDisplay createTestActivityDisplayInContainer() { + final TestActivityDisplay testActivityDisplay = createNewActivityDisplay(); + mRootActivityContainer.addChild(testActivityDisplay, POSITION_TOP); + return testActivityDisplay; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 44e998b7e62a..2263cf3a02e0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -152,6 +152,7 @@ public class WindowTestUtils { public static class TestAppWindowToken extends AppWindowToken { boolean mOnTop = false; private Transaction mPendingTransactionOverride; + boolean mSkipOnParentSet = true; private TestAppWindowToken(DisplayContent dc) { super(dc.mWmService, new IApplicationToken.Stub() { @@ -200,7 +201,9 @@ public class WindowTestUtils { @Override void onParentSet() { - // Do nothing. + if (!mSkipOnParentSet) { + super.onParentSet(); + } } @Override diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java index 2ed11fe92e15..fa472e2575f0 100644 --- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java +++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java @@ -18,11 +18,14 @@ package com.android.server.usage; import android.annotation.UserIdInt; import android.app.PendingIntent; +import android.app.usage.UsageStatsManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.UserHandle; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -163,6 +166,9 @@ public class AppTimeLimitController { /** Map of observerId to details of the time limit group */ SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>(); + /** Map of observerId to details of the app usage limit group */ + SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>(); + private ObserverAppData(int uid) { this.uid = uid; } @@ -177,6 +183,10 @@ public class AppTimeLimitController { sessionUsageGroups.remove(observerId); } + @GuardedBy("mLock") + void removeAppUsageLimitGroup(int observerId) { + appUsageLimitGroups.remove(observerId); + } @GuardedBy("mLock") void dump(PrintWriter pw) { @@ -194,6 +204,12 @@ public class AppTimeLimitController { sessionUsageGroups.valueAt(i).dump(pw); pw.println(); } + pw.println(" App Usage Limit Groups:"); + final int nAppUsageLimitGroups = appUsageLimitGroups.size(); + for (int i = 0; i < nAppUsageLimitGroups; i++) { + appUsageLimitGroups.valueAt(i).dump(pw); + pw.println(); + } } } @@ -493,6 +509,54 @@ public class AppTimeLimitController { } } + class AppUsageLimitGroup extends UsageGroup { + private boolean mGroupLimit; + + public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId, + String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) { + super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback); + mGroupLimit = observed.length > 1; + } + + @Override + @GuardedBy("mLock") + public void remove() { + super.remove(); + ObserverAppData observerApp = mObserverAppRef.get(); + if (observerApp != null) { + observerApp.removeAppUsageLimitGroup(mObserverId); + } + } + + @GuardedBy("mLock") + boolean isGroupLimit() { + return mGroupLimit; + } + + @GuardedBy("mLock") + long getTotaUsageLimit() { + return mTimeLimitMs; + } + + @GuardedBy("mLock") + long getUsageRemaining() { + // If there is currently an active session, account for its usage + if (mActives > 0) { + return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs); + } else { + return mTimeLimitMs - mUsageTimeMs; + } + } + + @Override + @GuardedBy("mLock") + void dump(PrintWriter pw) { + super.dump(pw); + pw.print(" groupLimit="); + pw.print(mGroupLimit); + } + } + private class MyHandler extends Handler { static final int MSG_CHECK_TIMEOUT = 1; @@ -553,6 +617,12 @@ public class AppTimeLimitController { /** Overrideable for testing purposes */ @VisibleForTesting + protected long getAppUsageLimitObserverPerUidLimit() { + return MAX_OBSERVER_PER_UID; + } + + /** Overrideable for testing purposes */ + @VisibleForTesting protected long getMinTimeLimit() { return ONE_MINUTE; } @@ -572,6 +642,61 @@ public class AppTimeLimitController { } } + @VisibleForTesting + AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) { + synchronized (mLock) { + return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get( + observerId); + } + } + + /** + * Returns an object describing the app usage limit for the given package which was set via + * {@link #addAppUsageLimitObserver). + * If there are multiple limits that apply to the package, the one with the smallest + * time remaining will be returned. + */ + public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit( + String packageName, UserHandle user) { + synchronized (mLock) { + final UserData userData = getOrCreateUserDataLocked(user.getIdentifier()); + if (userData == null) { + return null; + } + + final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName); + if (usageGroups == null || usageGroups.isEmpty()) { + return null; + } + + final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>(); + for (int i = 0; i < usageGroups.size(); i++) { + if (usageGroups.get(i) instanceof AppUsageLimitGroup) { + final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i); + for (int j = 0; j < group.mObserved.length; j++) { + if (group.mObserved[j].equals(packageName)) { + usageLimitGroups.add(group); + break; + } + } + } + } + if (usageLimitGroups.isEmpty()) { + return null; + } + + AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0); + for (int i = 1; i < usageLimitGroups.size(); i++) { + final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i); + if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) { + smallestGroup = otherGroup; + } + } + return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(), + smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining()); + } + } + /** Returns an existing UserData object for the given userId, or creates one */ @GuardedBy("mLock") private UserData getOrCreateUserDataLocked(int userId) { @@ -726,6 +851,61 @@ public class AppTimeLimitController { } /** + * Registers an app usage limit observer with the given details. + * Existing app usage limit observer with the same observerId will be removed. + */ + public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed, + long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) { + if (timeLimit < getMinTimeLimit()) { + throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit()); + } + synchronized (mLock) { + UserData user = getOrCreateUserDataLocked(userId); + ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId); + if (group != null) { + // Remove previous app usage group associated with observerId + group.remove(); + } + + final int observerIdCount = observerApp.appUsageLimitGroups.size(); + if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) { + throw new IllegalStateException( + "Too many app usage observers added by uid " + requestingUid); + } + group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit, + callbackIntent); + observerApp.appUsageLimitGroups.append(observerId, group); + + if (DEBUG) { + Slog.d(TAG, "addObserver " + observed + " for " + timeLimit); + } + + user.addUsageGroup(group); + noteActiveLocked(user, group, getUptimeMillis()); + } + } + + /** + * Remove a registered observer by observerId and calling uid. + * + * @param requestingUid The calling uid + * @param observerId The unique observer id for this user + * @param userId The user id of the observer + */ + public void removeAppUsageLimitObserver(int requestingUid, int observerId, + @UserIdInt int userId) { + synchronized (mLock) { + final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId); + if (group != null) { + // Remove previous app usage group associated with observerId + group.remove(); + } + } + } + + /** * Called when an entity becomes active. * * @param name The entity that became active diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 9a5bd1379717..f1ddfe4cd0d5 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -218,6 +218,14 @@ public class IntervalStats { case (int) IntervalStatsProto.Event.INSTANCE_ID: event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID); break; + case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX: + event.mTaskRootPackage = getCachedStringRef(stringPool.get( + parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1)); + break; + case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX: + event.mTaskRootClass = getCachedStringRef(stringPool.get( + parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1)); + break; case ProtoInputStream.NO_MORE_FIELDS: // Handle default values for certain events types switch (event.mEventType) { @@ -332,6 +340,12 @@ public class IntervalStats { if (event.mClass != null) { event.mClass = getCachedStringRef(event.mClass); } + if (event.mTaskRootPackage != null) { + event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage); + } + if (event.mTaskRootClass != null) { + event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass); + } if (event.mEventType == NOTIFICATION_INTERRUPTION) { event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java index d70653781eff..11d49eb40bc0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProto.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -442,6 +442,28 @@ final class UsageStatsProto { proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags); proto.write(IntervalStatsProto.Event.TYPE, event.mEventType); proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId); + if (event.mTaskRootPackage != null) { + final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage); + if (taskRootPackageIndex >= 0) { + proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX, + taskRootPackageIndex + 1); + } else { + // Task root package not in Stringpool for some reason. + Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage + + ") not found in IntervalStats string cache"); + } + } + if (event.mTaskRootClass != null) { + final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass); + if (taskRootClassIndex >= 0) { + proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX, + taskRootClassIndex + 1); + } else { + // Task root class not in Stringpool for some reason. + Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass + + ") not found in IntervalStats string cache"); + } + } switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: if (event.mConfiguration != null) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 76a3aa81530e..85939d498755 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -22,6 +22,8 @@ import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY; +import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY; import android.Manifest; import android.app.ActivityManager; @@ -39,6 +41,7 @@ import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManager.StandbyBuckets; +import android.app.usage.UsageStatsManager.UsageSource; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -65,6 +68,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -132,6 +136,7 @@ public class UsageStatsService extends SystemService implements private File mUsageStatsDir; long mRealTimeSnapshot; long mSystemTimeSnapshot; + int mUsageSource; /** Manages the standby state of apps. */ AppStandbyController mAppStandby; @@ -258,6 +263,7 @@ public class UsageStatsService extends SystemService implements } else { Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE); } + readUsageSourceSetting(); } } @@ -268,6 +274,13 @@ public class UsageStatsService extends SystemService implements return mDpmInternal; } + private void readUsageSourceSetting() { + synchronized (mLock) { + mUsageSource = Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY); + } + } + private class UserActionsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -459,12 +472,28 @@ public class UsageStatsService extends SystemService implements service.reportEvent(event); mAppStandby.reportEvent(event, elapsedRealtime, userId); + + String packageName; + + switch(mUsageSource) { + case USAGE_SOURCE_CURRENT_ACTIVITY: + packageName = event.getPackageName(); + break; + case USAGE_SOURCE_TASK_ROOT_ACTIVITY: + default: + packageName = event.getTaskRootPackageName(); + if (packageName == null) { + packageName = event.getPackageName(); + } + break; + } + switch (event.mEventType) { case Event.ACTIVITY_RESUMED: synchronized (mVisibleActivities) { mVisibleActivities.put(event.mInstanceId, event.getClassName()); try { - mAppTimeLimit.noteUsageStart(event.getPackageName(), userId); + mAppTimeLimit.noteUsageStart(packageName, userId); } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to note usage start", iae); } @@ -496,7 +525,7 @@ public class UsageStatsService extends SystemService implements synchronized (mVisibleActivities) { if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) { try { - mAppTimeLimit.noteUsageStop(event.getPackageName(), userId); + mAppTimeLimit.noteUsageStop(packageName, userId); } catch (IllegalArgumentException iae) { Slog.w(TAG, "Failed to note usage stop", iae); } @@ -638,7 +667,7 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime, - String packageName) { + String packageName, boolean includeTaskRoot) { synchronized (mLock) { final long timeNow = checkAndGetTimeLocked(); if (!validRange(timeNow, beginTime, endTime)) { @@ -647,7 +676,7 @@ public class UsageStatsService extends SystemService implements final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId, timeNow); - return service.queryEventsForPackage(beginTime, endTime, packageName); + return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot); } } @@ -738,6 +767,10 @@ public class UsageStatsService extends SystemService implements mAppStandby.dumpState(args, pw); } + idpw.println(); + idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource)); + idpw.println(); + mAppTimeLimit.dump(null, pw); } } @@ -808,7 +841,7 @@ public class UsageStatsService extends SystemService implements return mode == AppOpsManager.MODE_ALLOWED; } - private boolean hasObserverPermission(String callingPackage) { + private boolean hasObserverPermission() { final int callingUid = Binder.getCallingUid(); DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (callingUid == Process.SYSTEM_UID @@ -822,6 +855,22 @@ public class UsageStatsService extends SystemService implements == PackageManager.PERMISSION_GRANTED; } + private boolean hasPermissions(String callingPackage, String... permissions) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SYSTEM_UID) { + // Caller is the system, so proceed. + return true; + } + + boolean hasPermissions = true; + final Context context = getContext(); + for (int i = 0; i < permissions.length; i++) { + hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i]) + == PackageManager.PERMISSION_GRANTED); + } + return hasPermissions; + } + private void checkCallerIsSystemOrSameApp(String pkg) { if (isCallingUidSystem()) { return; @@ -939,10 +988,12 @@ public class UsageStatsService extends SystemService implements final int callingUserId = UserHandle.getUserId(callingUid); checkCallerIsSameApp(callingPackage); + final boolean includeTaskRoot = hasPermission(callingPackage); + final long token = Binder.clearCallingIdentity(); try { return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime, - endTime, callingPackage); + endTime, callingPackage, includeTaskRoot); } finally { Binder.restoreCallingIdentity(token); } @@ -989,7 +1040,7 @@ public class UsageStatsService extends SystemService implements final long token = Binder.clearCallingIdentity(); try { return UsageStatsService.this.queryEventsForPackage(userId, beginTime, - endTime, pkg); + endTime, pkg, true); } finally { Binder.restoreCallingIdentity(token); } @@ -1229,7 +1280,7 @@ public class UsageStatsService extends SystemService implements public void registerAppUsageObserver(int observerId, String[] packages, long timeLimitMs, PendingIntent callbackIntent, String callingPackage) { - if (!hasObserverPermission(callingPackage)) { + if (!hasObserverPermission()) { throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); } @@ -1252,7 +1303,7 @@ public class UsageStatsService extends SystemService implements @Override public void unregisterAppUsageObserver(int observerId, String callingPackage) { - if (!hasObserverPermission(callingPackage)) { + if (!hasObserverPermission()) { throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); } @@ -1271,7 +1322,7 @@ public class UsageStatsService extends SystemService implements long timeLimitMs, long sessionThresholdTimeMs, PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent, String callingPackage) { - if (!hasObserverPermission(callingPackage)) { + if (!hasObserverPermission()) { throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); } @@ -1295,7 +1346,7 @@ public class UsageStatsService extends SystemService implements @Override public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) { - if (!hasObserverPermission(callingPackage)) { + if (!hasObserverPermission()) { throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); } @@ -1311,6 +1362,51 @@ public class UsageStatsService extends SystemService implements } @Override + public void registerAppUsageLimitObserver(int observerId, String[] packages, + long timeLimitMs, PendingIntent callbackIntent, String callingPackage) { + if (!hasPermissions(callingPackage, + Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) { + throw new SecurityException("Caller doesn't have both SUSPEND_APPS and " + + "OBSERVE_APP_USAGE permissions"); + } + + if (packages == null || packages.length == 0) { + throw new IllegalArgumentException("Must specify at least one package"); + } + if (callbackIntent == null) { + throw new NullPointerException("callbackIntent can't be null"); + } + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final long token = Binder.clearCallingIdentity(); + try { + UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId, + packages, timeLimitMs, callbackIntent, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) { + if (!hasPermissions(callingPackage, + Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) { + throw new SecurityException("Caller doesn't have both SUSPEND_APPS and " + + "OBSERVE_APP_USAGE permissions"); + } + + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final long token = Binder.clearCallingIdentity(); + try { + UsageStatsService.this.unregisterAppUsageLimitObserver( + callingUid, observerId, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void reportUsageStart(IBinder activity, String token, String callingPackage) { reportPastUsageStart(activity, token, 0, callingPackage); } @@ -1373,6 +1469,21 @@ public class UsageStatsService extends SystemService implements Binder.restoreCallingIdentity(binderToken); } } + + @Override + public @UsageSource int getUsageSource() { + if (!hasObserverPermission()) { + throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); + } + synchronized (mLock) { + return mUsageSource; + } + } + + @Override + public void forceUsageSourceSettingRead() { + readUsageSourceSetting(); + } } void registerAppUsageObserver(int callingUid, int observerId, String[] packages, @@ -1397,6 +1508,16 @@ public class UsageStatsService extends SystemService implements mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId); } + void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages, + long timeLimitMs, PendingIntent callbackIntent, int userId) { + mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs, + callbackIntent, userId); + } + + void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) { + mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId); + } + /** * This local service implementation is primarily used by ActivityManagerService. * ActivityManagerService will call these methods holding the 'am' lock, which means we @@ -1406,7 +1527,7 @@ public class UsageStatsService extends SystemService implements @Override public void reportEvent(ComponentName component, int userId, int eventType, - int instanceId) { + int instanceId, ComponentName taskRoot) { if (component == null) { Slog.w(TAG, "Event reported without a component name"); return; @@ -1416,6 +1537,13 @@ public class UsageStatsService extends SystemService implements event.mPackage = component.getPackageName(); event.mClass = component.getClassName(); event.mInstanceId = instanceId; + if (taskRoot == null) { + event.mTaskRootPackage = null; + event.mTaskRootClass = null; + } else { + event.mTaskRootPackage = taskRoot.getPackageName(); + event.mTaskRootClass = taskRoot.getClassName(); + } mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1595,5 +1723,10 @@ public class UsageStatsService extends SystemService implements public void reportExemptedSyncStart(String packageName, int userId) { mAppStandby.postReportExemptedSyncStart(packageName, userId); } + + @Override + public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) { + return mAppTimeLimit.getAppUsageLimit(packageName, user); + } } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 2d1098c7cf5c..d52d32faa7d1 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -401,7 +401,7 @@ class UserUsageStatsService { } UsageEvents queryEvents(final long beginTime, final long endTime, - boolean obfuscateInstantApps) { + boolean obfuscateInstantApps) { final ArraySet<String> names = new ArraySet<>(); List<Event> results = queryStats(INTERVAL_DAILY, beginTime, endTime, new StatCombiner<Event>() { @@ -425,6 +425,12 @@ class UserUsageStatsService { if (event.mClass != null) { names.add(event.mClass); } + if (event.mTaskRootPackage != null) { + names.add(event.mTaskRootPackage); + } + if (event.mTaskRootClass != null) { + names.add(event.mTaskRootClass); + } accumulatedResult.add(event); } } @@ -436,11 +442,11 @@ class UserUsageStatsService { String[] table = names.toArray(new String[names.size()]); Arrays.sort(table); - return new UsageEvents(results, table); + return new UsageEvents(results, table, true); } UsageEvents queryEventsForPackage(final long beginTime, final long endTime, - final String packageName) { + final String packageName, boolean includeTaskRoot) { final ArraySet<String> names = new ArraySet<>(); names.add(packageName); final List<Event> results = queryStats(INTERVAL_DAILY, @@ -459,6 +465,12 @@ class UserUsageStatsService { if (event.mClass != null) { names.add(event.mClass); } + if (includeTaskRoot && event.mTaskRootPackage != null) { + names.add(event.mTaskRootPackage); + } + if (includeTaskRoot && event.mTaskRootClass != null) { + names.add(event.mTaskRootClass); + } accumulatedResult.add(event); } }); @@ -469,7 +481,7 @@ class UserUsageStatsService { final String[] table = names.toArray(new String[names.size()]); Arrays.sort(table); - return new UsageEvents(results, table); + return new UsageEvents(results, table, includeTaskRoot); } void persistActiveStats() { @@ -684,6 +696,14 @@ class UserUsageStatsService { pw.printPair("instanceId", event.getInstanceId()); } + if (event.getTaskRootPackageName() != null) { + pw.printPair("taskRootPackage", event.getTaskRootPackageName()); + } + + if (event.getTaskRootClassName() != null) { + pw.printPair("taskRootClass", event.getTaskRootClassName()); + } + if (event.mNotificationChannelId != null) { pw.printPair("channelId", event.mNotificationChannelId); } diff --git a/services/usb/Android.bp b/services/usb/Android.bp index feb7b76ae119..20855b70cabc 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -10,6 +10,7 @@ java_library_static { static_libs: [ "android.hardware.usb-V1.0-java", "android.hardware.usb-V1.1-java", + "android.hardware.usb-V1.2-java", "android.hardware.usb.gadget-V1.0-java", ], } diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 6f210e37d6d1..50e4faab76e3 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; import static android.hardware.usb.UsbPortStatus.MODE_DFP; @@ -29,19 +31,23 @@ import static com.android.internal.usb.DumpUtils.writePortStatus; import android.Manifest; import android.annotation.NonNull; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; -import android.hardware.usb.V1_0.IUsb; import android.hardware.usb.V1_0.PortRole; import android.hardware.usb.V1_0.PortRoleType; -import android.hardware.usb.V1_0.PortStatus; import android.hardware.usb.V1_0.Status; -import android.hardware.usb.V1_1.IUsbCallback; import android.hardware.usb.V1_1.PortStatus_1_1; +import android.hardware.usb.V1_2.IUsb; +import android.hardware.usb.V1_2.IUsbCallback; +import android.hardware.usb.V1_2.PortStatus; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.os.Bundle; @@ -55,18 +61,20 @@ import android.os.SystemClock; import android.os.UserHandle; import android.service.usb.UsbPortInfoProto; import android.service.usb.UsbPortManagerProto; +import android.service.usb.UsbServiceProto; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import java.util.ArrayList; -import java.util.HashMap; import java.util.NoSuchElementException; /** @@ -85,6 +93,7 @@ public class UsbPortManager { private static final String TAG = "UsbPortManager"; private static final int MSG_UPDATE_PORTS = 1; + private static final int MSG_SYSTEM_READY = 2; // All non-trivial role combinations. private static final int COMBO_SOURCE_HOST = @@ -132,7 +141,19 @@ public class UsbPortManager { // Maintains the current connected status of the port. // Uploads logs only when the connection status is changes. - private final HashMap<String, Boolean> mConnected = new HashMap<>(); + private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>(); + + // Maintains the USB contaminant status that was previously logged. + // Logs get uploaded only when contaminant presence status changes. + private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>(); + + private NotificationManager mNotificationManager; + + /** + * If there currently is a notification about contaminated USB port shown the id of the + * notification, or 0 if there is none. + */ + private int mIsPortContaminatedNotificationId; public UsbPortManager(Context context) { mContext = context; @@ -164,6 +185,90 @@ public class UsbPortManager { "ServiceStart: Failed to query port status", e); } } + mHandler.sendEmptyMessage(MSG_SYSTEM_READY); + } + + private void updateContaminantNotification() { + PortInfo currentPortInfo = null; + Resources r = mContext.getResources(); + + // Not handling multiple ports here. Showing the notification + // for the first port that returns CONTAMINANT_PRESENCE_DETECTED. + for (PortInfo portInfo : mPorts.values()) { + if (portInfo.mUsbPortStatus.getContaminantDetectionStatus() + == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED) { + currentPortInfo = portInfo; + break; + } + } + + if (currentPortInfo != null && mIsPortContaminatedNotificationId + != SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) { + if (mIsPortContaminatedNotificationId + == SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED) { + mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId, + UserHandle.ALL); + } + + mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_DETECTED; + int titleRes = com.android.internal.R.string.usb_contaminant_detected_title; + CharSequence title = r.getText(titleRes); + String channel = SystemNotificationChannels.ALERTS; + CharSequence message = r.getText( + com.android.internal.R.string.usb_contaminant_detected_message); + + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbContaminantActivity"); + intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort)); + + PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, + intent, 0, null, UserHandle.CURRENT); + + Notification.Builder builder = new Notification.Builder(mContext, channel) + .setOngoing(true) + .setTicker(title) + .setColor(mContext.getColor( + com.android.internal.R.color + .system_notification_accent_color)) + .setContentIntent(pi) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setStyle(new Notification.BigTextStyle() + .bigText(message)); + Notification notification = builder.build(); + mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification, + UserHandle.ALL); + } else if (currentPortInfo == null && mIsPortContaminatedNotificationId + == SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) { + mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId, + UserHandle.ALL); + + mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED; + int titleRes = com.android.internal.R.string.usb_contaminant_not_detected_title; + CharSequence title = r.getText(titleRes); + String channel = SystemNotificationChannels.ALERTS; + CharSequence message = r.getText( + com.android.internal.R.string.usb_contaminant_not_detected_message); + + Notification.Builder builder = new Notification.Builder(mContext, channel) + .setSmallIcon(com.android.internal.R.drawable.ic_usb_48dp) + .setTicker(title) + .setColor(mContext.getColor( + com.android.internal.R.color + .system_notification_accent_color)) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setStyle(new Notification.BigTextStyle() + .bigText(message)); + Notification notification = builder.build(); + mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification, + UserHandle.ALL); + } } public UsbPort[] getPorts() { @@ -184,6 +289,43 @@ public class UsbPortManager { } } + /** + * Enables/disables contaminant detection. + * + * @param portId port identifier. + * @param enable enable contaminant detection when set to true. + */ + public void enableContaminantDetection(@NonNull String portId, boolean enable, + @NonNull IndentingPrintWriter pw) { + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + if (pw != null) { + pw.println("No such USB port: " + portId); + } + return; + } + + if (!portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) { + return; + } + + if ((enable && portInfo.mUsbPortStatus.getContaminantDetectionStatus() + != UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) || (!enable + && portInfo.mUsbPortStatus.getContaminantDetectionStatus() + == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) + || (portInfo.mUsbPortStatus.getContaminantDetectionStatus() + == UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED)) { + return; + } + + try { + // Oneway call into the hal + mProxy.enableContaminantPresenceDetection(portId, enable); + } catch (RemoteException e) { + logAndPrintException(null, "Failed to set contaminant detection", e); + } + } + public void setPortRoles(String portId, int newPowerRole, int newDataRole, IndentingPrintWriter pw) { synchronized (mLock) { @@ -371,6 +513,27 @@ public class UsbPortManager { } } + /** + * Sets contaminant status for simulated USB port objects. + */ + public void simulateContaminantStatus(String portId, boolean detected, + IndentingPrintWriter pw) { + synchronized (mLock) { + final RawPortInfo portInfo = mSimulatedPorts.get(portId); + if (portInfo == null) { + pw.println("Simulated port not found."); + return; + } + + pw.println("Simulating wet port: portId=" + portId + + ", wet=" + detected); + portInfo.contaminantDetectionStatus = detected + ? UsbPortStatus.CONTAMINANT_DETECTION_DETECTED + : UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED; + updatePortsLocked(pw, null); + } + } + public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) { synchronized (mLock) { final RawPortInfo portInfo = mSimulatedPorts.get(portId); @@ -441,7 +604,8 @@ public class UsbPortManager { this.portManager = portManager; } - public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) { + public void notifyPortStatusChange( + ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) { if (!portManager.mSystemReady) { return; } @@ -453,14 +617,17 @@ public class UsbPortManager { ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - for (PortStatus current : currentPortStatus) { + for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) { RawPortInfo temp = new RawPortInfo(current.portName, - current.supportedModes, current.currentMode, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, current.canChangeMode, current.currentPowerRole, current.canChangePowerRole, - current.currentDataRole, current.canChangeDataRole); + current.currentDataRole, current.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED); newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName); + logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName); } Message message = portManager.mHandler.obtainMessage(); @@ -485,14 +652,61 @@ public class UsbPortManager { ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - for (PortStatus_1_1 current : currentPortStatus) { + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus_1_1 current = currentPortStatus.get(i); RawPortInfo temp = new RawPortInfo(current.status.portName, - current.supportedModes, current.currentMode, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, current.status.canChangeMode, current.status.currentPowerRole, current.status.canChangePowerRole, - current.status.currentDataRole, current.status.canChangeDataRole); + current.status.currentDataRole, current.status.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED); + newPortInfo.add(temp); + logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName); + } + + Message message = portManager.mHandler.obtainMessage(); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(PORT_INFO, newPortInfo); + message.what = MSG_UPDATE_PORTS; + message.setData(bundle); + portManager.mHandler.sendMessage(message); + } + + public void notifyPortStatusChange_1_2( + ArrayList<PortStatus> currentPortStatus, int retval) { + if (!portManager.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + logAndPrint(Log.ERROR, pw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus current = currentPortStatus.get(i); + RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName, + current.status_1_1.supportedModes, + current.supportedContaminantProtectionModes, + current.status_1_1.currentMode, + current.status_1_1.status.canChangeMode, + current.status_1_1.status.currentPowerRole, + current.status_1_1.status.canChangePowerRole, + current.status_1_1.status.currentDataRole, + current.status_1_1.status.canChangeDataRole, + current.supportsEnableContaminantPresenceProtection, + current.contaminantProtectionStatus, + current.supportsEnableContaminantPresenceDetection, + current.contaminantDetectionStatus); newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback: " + current.status.portName); + logAndPrint(Log.INFO, pw, "ClientCallback V1_2: " + + current.status_1_1.status.portName); } Message message = portManager.mHandler.obtainMessage(); @@ -573,16 +787,26 @@ public class UsbPortManager { for (int i = 0; i < count; i++) { final RawPortInfo portInfo = mSimulatedPorts.valueAt(i); addOrUpdatePortLocked(portInfo.portId, portInfo.supportedModes, + portInfo.supportedContaminantProtectionModes, portInfo.currentMode, portInfo.canChangeMode, portInfo.currentPowerRole, portInfo.canChangePowerRole, - portInfo.currentDataRole, portInfo.canChangeDataRole, pw); + portInfo.currentDataRole, portInfo.canChangeDataRole, + portInfo.supportsEnableContaminantPresenceProtection, + portInfo.contaminantProtectionStatus, + portInfo.supportsEnableContaminantPresenceDetection, + portInfo.contaminantDetectionStatus, pw); } } else { for (RawPortInfo currentPortInfo : newPortInfo) { addOrUpdatePortLocked(currentPortInfo.portId, currentPortInfo.supportedModes, + currentPortInfo.supportedContaminantProtectionModes, currentPortInfo.currentMode, currentPortInfo.canChangeMode, currentPortInfo.currentPowerRole, currentPortInfo.canChangePowerRole, - currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole, pw); + currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole, + currentPortInfo.supportsEnableContaminantPresenceProtection, + currentPortInfo.contaminantProtectionStatus, + currentPortInfo.supportsEnableContaminantPresenceDetection, + currentPortInfo.contaminantDetectionStatus, pw); } } @@ -608,12 +832,16 @@ public class UsbPortManager { } } - // Must only be called by updatePortsLocked. private void addOrUpdatePortLocked(String portId, int supportedModes, + int supportedContaminantProtectionModes, int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, + boolean supportsEnableContaminantPresenceProtection, + int contaminantProtectionStatus, + boolean supportsEnableContaminantPresenceDetection, + int contaminantDetectionStatus, IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. @@ -664,12 +892,15 @@ public class UsbPortManager { // Update the port data structures. PortInfo portInfo = mPorts.get(portId); if (portInfo == null) { - portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId, - supportedModes); + portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), + portId, supportedModes, supportedContaminantProtectionModes, + supportsEnableContaminantPresenceProtection, + supportsEnableContaminantPresenceDetection); portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, - supportedRoleCombinations); + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus); mPorts.put(portId, portInfo); } else { // Sanity check that ports aren't changing definition out from under us. @@ -681,10 +912,32 @@ public class UsbPortManager { + ", current=" + UsbPort.modeToString(supportedModes)); } + if (supportsEnableContaminantPresenceProtection + != portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()) { + logAndPrint(Log.WARN, pw, + "Ignoring inconsistent supportsEnableContaminantPresenceProtection" + + "USB port driver (should be immutable): " + + "previous=" + + portInfo.mUsbPort.supportsEnableContaminantPresenceProtection() + + ", current=" + supportsEnableContaminantPresenceProtection); + } + + if (supportsEnableContaminantPresenceDetection + != portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) { + logAndPrint(Log.WARN, pw, + "Ignoring inconsistent supportsEnableContaminantPresenceDetection " + + "USB port driver (should be immutable): " + + "previous=" + + portInfo.mUsbPort.supportsEnableContaminantPresenceDetection() + + ", current=" + supportsEnableContaminantPresenceDetection); + } + + if (portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, - supportedRoleCombinations)) { + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -695,16 +948,37 @@ public class UsbPortManager { private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port added: " + portInfo); sendPortChangedBroadcastLocked(portInfo); + updateContaminantNotification(); } private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo); sendPortChangedBroadcastLocked(portInfo); + updateContaminantNotification(); } private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo); sendPortChangedBroadcastLocked(portInfo); + updateContaminantNotification(); + } + + // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus + // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED + // whereas HAL policy is against a loosely defined constant. + private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) { + switch (contaminantDetectionStatus) { + case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED: + return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED; + case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED: + return UsbServiceProto.CONTAMINANT_STATUS_DISABLED; + case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED: + return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED; + case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED: + return UsbServiceProto.CONTAMINANT_STATUS_DETECTED; + default: + return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN; + } } private void sendPortChangedBroadcastLocked(PortInfo portInfo) { @@ -721,6 +995,33 @@ public class UsbPortManager { Manifest.permission.MANAGE_USB)); // Log to statsd + + // Port is removed + if (portInfo.mUsbPortStatus == null) { + if (mConnected.containsKey(portInfo.mUsbPort.getId())) { + //Previous logged a connected. Set it to disconnected. + if (mConnected.get(portInfo.mUsbPort.getId())) { + StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED, + StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, + portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); + } + mConnected.remove(portInfo.mUsbPort.getId()); + } + + if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) { + //Previous logged a contaminant detected. Set it to not detected. + if ((mContaminantStatus.get(portInfo.mUsbPort.getId()) + == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) { + StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + portInfo.mUsbPort.getId(), + convertContaminantDetectionStatusToProto( + UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED)); + } + mContaminantStatus.remove(portInfo.mUsbPort.getId()); + } + return; + } + if (!mConnected.containsKey(portInfo.mUsbPort.getId()) || (mConnected.get(portInfo.mUsbPort.getId()) != portInfo.mUsbPortStatus.isConnected())) { @@ -731,6 +1032,17 @@ public class UsbPortManager { StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); } + + if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId()) + || (mContaminantStatus.get(portInfo.mUsbPort.getId()) + != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) { + mContaminantStatus.put(portInfo.mUsbPort.getId(), + portInfo.mUsbPortStatus.getContaminantDetectionStatus()); + StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + portInfo.mUsbPort.getId(), + convertContaminantDetectionStatusToProto( + portInfo.mUsbPortStatus.getContaminantDetectionStatus())); + } } private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { @@ -759,6 +1071,11 @@ public class UsbPortManager { } break; } + case MSG_SYSTEM_READY: { + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + break; + } } } }; @@ -784,8 +1101,14 @@ public class UsbPortManager { // 0 when port is connected. Else reports the last connected duration public long mLastConnectDurationMillis; - PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes) { - mUsbPort = new UsbPort(usbManager, portId, supportedModes); + PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes, + int supportedContaminantProtectionModes, + boolean supportsEnableContaminantPresenceDetection, + boolean supportsEnableContaminantPresenceProtection) { + mUsbPort = new UsbPort(usbManager, portId, supportedModes, + supportedContaminantProtectionModes, + supportsEnableContaminantPresenceDetection, + supportsEnableContaminantPresenceProtection); } public boolean setStatus(int currentMode, boolean canChangeMode, @@ -804,7 +1127,45 @@ public class UsbPortManager { || mUsbPortStatus.getSupportedRoleCombinations() != supportedRoleCombinations) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, - supportedRoleCombinations); + supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE, + UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED); + dispositionChanged = true; + } + + if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) { + mConnectedAtMillis = SystemClock.elapsedRealtime(); + mLastConnectDurationMillis = 0; + } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) { + mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis; + mConnectedAtMillis = 0; + } + + return dispositionChanged; + } + + public boolean setStatus(int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus) { + boolean dispositionChanged = false; + + mCanChangeMode = canChangeMode; + mCanChangePowerRole = canChangePowerRole; + mCanChangeDataRole = canChangeDataRole; + if (mUsbPortStatus == null + || mUsbPortStatus.getCurrentMode() != currentMode + || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole + || mUsbPortStatus.getCurrentDataRole() != currentDataRole + || mUsbPortStatus.getSupportedRoleCombinations() + != supportedRoleCombinations + || mUsbPortStatus.getContaminantProtectionStatus() + != contaminantProtectionStatus + || mUsbPortStatus.getContaminantDetectionStatus() + != contaminantDetectionStatus) { + mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus); dispositionChanged = true; } @@ -855,32 +1216,54 @@ public class UsbPortManager { private static final class RawPortInfo implements Parcelable { public final String portId; public final int supportedModes; + public final int supportedContaminantProtectionModes; public int currentMode; public boolean canChangeMode; public int currentPowerRole; public boolean canChangePowerRole; public int currentDataRole; public boolean canChangeDataRole; + public boolean supportsEnableContaminantPresenceProtection; + public int contaminantProtectionStatus; + public boolean supportsEnableContaminantPresenceDetection; + public int contaminantDetectionStatus; RawPortInfo(String portId, int supportedModes) { this.portId = portId; this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceProtection = false; + this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceDetection = false; + this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; } - RawPortInfo(String portId, int supportedModes, + RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, - int currentDataRole, boolean canChangeDataRole) { + int currentDataRole, boolean canChangeDataRole, + boolean supportsEnableContaminantPresenceProtection, + int contaminantProtectionStatus, + boolean supportsEnableContaminantPresenceDetection, + int contaminantDetectionStatus) { this.portId = portId; this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; this.currentMode = currentMode; this.canChangeMode = canChangeMode; this.currentPowerRole = currentPowerRole; this.canChangePowerRole = canChangePowerRole; this.currentDataRole = currentDataRole; this.canChangeDataRole = canChangeDataRole; + this.supportsEnableContaminantPresenceProtection = + supportsEnableContaminantPresenceProtection; + this.contaminantProtectionStatus = contaminantProtectionStatus; + this.supportsEnableContaminantPresenceDetection = + supportsEnableContaminantPresenceDetection; + this.contaminantDetectionStatus = contaminantDetectionStatus; } + @Override public int describeContents() { return 0; @@ -890,35 +1273,50 @@ public class UsbPortManager { public void writeToParcel(Parcel dest, int flags) { dest.writeString(portId); dest.writeInt(supportedModes); + dest.writeInt(supportedContaminantProtectionModes); dest.writeInt(currentMode); dest.writeByte((byte) (canChangeMode ? 1 : 0)); dest.writeInt(currentPowerRole); dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); dest.writeInt(currentDataRole); dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); + dest.writeBoolean(supportsEnableContaminantPresenceProtection); + dest.writeInt(contaminantProtectionStatus); + dest.writeBoolean(supportsEnableContaminantPresenceDetection); + dest.writeInt(contaminantDetectionStatus); } public static final Parcelable.Creator<RawPortInfo> CREATOR = new Parcelable.Creator<RawPortInfo>() { - @Override - public RawPortInfo createFromParcel(Parcel in) { - String id = in.readString(); - int supportedModes = in.readInt(); - int currentMode = in.readInt(); - boolean canChangeMode = in.readByte() != 0; - int currentPowerRole = in.readInt(); - boolean canChangePowerRole = in.readByte() != 0; - int currentDataRole = in.readInt(); - boolean canChangeDataRole = in.readByte() != 0; - return new RawPortInfo(id, supportedModes, currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole); - } + @Override + public RawPortInfo createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + int supportedContaminantProtectionModes = in.readInt(); + int currentMode = in.readInt(); + boolean canChangeMode = in.readByte() != 0; + int currentPowerRole = in.readInt(); + boolean canChangePowerRole = in.readByte() != 0; + int currentDataRole = in.readInt(); + boolean canChangeDataRole = in.readByte() != 0; + boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); + int contaminantProtectionStatus = in.readInt(); + boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); + int contaminantDetectionStatus = in.readInt(); + return new RawPortInfo(id, supportedModes, + supportedContaminantProtectionModes, currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportsEnableContaminantPresenceProtection, + contaminantProtectionStatus, + supportsEnableContaminantPresenceDetection, + contaminantDetectionStatus); + } - @Override - public RawPortInfo[] newArray(int size) { - return new RawPortInfo[size]; - } - }; + @Override + public RawPortInfo[] newArray(int size) { + return new RawPortInfo[size]; + } + }; } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 911547720a8f..4be68b83dbcb 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -554,6 +554,21 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void enableContaminantDetection(String portId, boolean enable) { + Preconditions.checkNotNull(portId, "portId must not be null"); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long ident = Binder.clearCallingIdentity(); + try { + if (mPortManager != null) { + mPortManager.enableContaminantDetection(portId, enable, null); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); synchronized (mLock) { @@ -747,6 +762,15 @@ public class UsbService extends IUsbManager.Stub { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), "", 0); } + } else if ("set-contaminant-status".equals(args[0]) && args.length == 3) { + final String portId = args[1]; + final Boolean wet = Boolean.parseBoolean(args[2]); + if (mPortManager != null) { + mPortManager.simulateContaminantStatus(portId, wet, pw); + pw.println(); + mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), + "", 0); + } } else if ("ports".equals(args[0]) && args.length == 1) { if (mPortManager != null) { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), @@ -791,6 +815,11 @@ public class UsbService extends IUsbManager.Stub { pw.println(" dumpsys usb connect-port \"matrix\" ufp sink device"); pw.println(" dumpsys usb reset"); pw.println(); + pw.println("Example simulate contaminant status:"); + pw.println(" dumpsys usb add-port \"matrix\" ufp"); + pw.println(" dumpsys usb set-contaminant-status \"matrix\" true"); + pw.println(" dumpsys usb set-contaminant-status \"matrix\" false"); + pw.println(); pw.println("Example USB device descriptors:"); pw.println(" dumpsys usb dump-descriptors -dump-short"); pw.println(" dumpsys usb dump-descriptors -dump-tree"); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 8c82cc835ed9..697469a3c680 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -939,11 +939,7 @@ public class SoundTriggerService extends SystemService { runOrAddOperation(new Operation( // always execute: () -> { - // Don't remove the callback if multiple triggers are allowed or - // if this event was triggered by a getModelState request - if (!mRecognitionConfig.allowMultipleTriggers - && event.status - != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { + if (!mRecognitionConfig.allowMultipleTriggers) { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 613c4ffceffc..718f2d379883 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -17,16 +17,18 @@ package com.android.server.voiceinteraction; import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; - -import com.android.internal.app.IVoiceActionCheckCallback; -import com.android.server.wm.ActivityTaskManagerInternal; import android.app.AppGlobals; +import android.app.role.OnRoleHoldersChangedListener; +import android.app.role.RoleManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -62,8 +64,9 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import com.android.internal.app.IVoiceInteractionSessionListener; +import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; @@ -75,10 +78,12 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.soundtrigger.SoundTriggerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.Executor; /** * SystemService that publishes an IVoiceInteractionManagerService. @@ -201,6 +206,7 @@ public class VoiceInteractionManagerService extends SystemService { VoiceInteractionManagerServiceStub() { mEnableService = shouldEnableService(mContext); + new RoleObserver(mContext.getMainExecutor()); } // TODO: VI Make sure the caller is the current user or profile @@ -1205,6 +1211,57 @@ public class VoiceInteractionManagerService extends SystemService { mSoundTriggerInternal.dump(fd, pw, args); } + @Override + public void setTranscription(String transcription) { + synchronized (this) { + final int size = mVoiceInteractionSessionListeners.beginBroadcast(); + for (int i = 0; i < size; ++i) { + final IVoiceInteractionSessionListener listener = + mVoiceInteractionSessionListeners.getBroadcastItem(i); + try { + listener.onTranscriptionUpdate(transcription); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering voice transcription.", e); + } + } + mVoiceInteractionSessionListeners.finishBroadcast(); + } + } + + @Override + public void clearTranscription(boolean immediate) { + synchronized (this) { + final int size = mVoiceInteractionSessionListeners.beginBroadcast(); + for (int i = 0; i < size; ++i) { + final IVoiceInteractionSessionListener listener = + mVoiceInteractionSessionListeners.getBroadcastItem(i); + try { + listener.onTranscriptionComplete(immediate); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering transcription complete event.", e); + } + } + mVoiceInteractionSessionListeners.finishBroadcast(); + } + } + + @Override + public void setVoiceState(int state) { + synchronized (this) { + final int size = mVoiceInteractionSessionListeners.beginBroadcast(); + for (int i = 0; i < size; ++i) { + final IVoiceInteractionSessionListener listener = + mVoiceInteractionSessionListeners.getBroadcastItem(i); + try { + listener.onVoiceStateChange(state); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering voice state change.", e); + } + } + mVoiceInteractionSessionListeners.finishBroadcast(); + } + } + private void enforceCallingPermission(String permission) { if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { @@ -1218,6 +1275,106 @@ public class VoiceInteractionManagerService extends SystemService { getActiveServiceComponentName()); } + class RoleObserver implements OnRoleHoldersChangedListener { + private PackageManager mPm = mContext.getPackageManager(); + private RoleManager mRm = mContext.getSystemService(RoleManager.class); + + RoleObserver(@NonNull @CallbackExecutor Executor executor) { + mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL); + } + + private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) { + ResolveInfo resolveInfo = mPm.resolveServiceAsUser( + new Intent(RecognitionService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA, user.getIdentifier()); + + if (resolveInfo == null || resolveInfo.serviceInfo == null) { + Log.w(TAG, "Unable to resolve default voice recognition service."); + return ""; + } + + return new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToShortString(); + } + + /** + * Convert the assistant-role holder into settings. The rest of the system uses the + * settings. + * + * @param roleName the name of the role whose holders are changed + * @param user the user for this role holder change + */ + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + if (!roleName.equals(RoleManager.ROLE_ASSISTANT)) { + return; + } + + List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user); + + if (roleHolders.isEmpty()) { + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer(user)); + } else { + // Assistant is singleton role + String pkg = roleHolders.get(0); + + // Try to set role holder as VoiceInteractionService + List<ResolveInfo> services = mPm.queryIntentServicesAsUser( + new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg), + PackageManager.GET_META_DATA, user.getIdentifier()); + + for (ResolveInfo resolveInfo : services) { + ServiceInfo serviceInfo = resolveInfo.serviceInfo; + + VoiceInteractionServiceInfo voiceInteractionServiceInfo = + new VoiceInteractionServiceInfo(mPm, serviceInfo); + if (!voiceInteractionServiceInfo.getSupportsAssist()) { + continue; + } + + String serviceComponentName = serviceInfo.getComponentName() + .flattenToShortString(); + + String serviceRecognizerName = new ComponentName(pkg, + voiceInteractionServiceInfo.getRecognitionService()) + .flattenToShortString(); + + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, serviceComponentName); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName); + + return; + } + + // If no service could be found try to set assist activity + final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser( + new Intent(Intent.ACTION_ASSIST).setPackage(pkg), + PackageManager.MATCH_DEFAULT_ONLY, user.getIdentifier()); + + for (ResolveInfo resolveInfo : activities) { + ActivityInfo activityInfo = resolveInfo.activityInfo; + + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, + activityInfo.getComponentName().flattenToShortString()); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, + getDefaultRecognizer(user)); + } + } + } + } + class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { super(handler); diff --git a/startop/OWNERS b/startop/OWNERS index 762cd8e48be4..5cf95825d11b 100644 --- a/startop/OWNERS +++ b/startop/OWNERS @@ -2,5 +2,5 @@ chriswailes@google.com eholk@google.com iam@google.com -sehr@google.com mathieuc@google.com +sehr@google.com diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java index c2e4581285fd..acf994610182 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java +++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java @@ -24,9 +24,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; -// TODO: fix this. either move this class into system server or add a dependency on -// these wm classes to libiorap-java and libiorap-java-tests (somehow). import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; @@ -113,12 +112,12 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected void writeToParcelImpl(Parcel p, int flags) { super.writeToParcelImpl(p, flags); - intent.writeToParcel(p, flags); + IntentProtoParcelable.write(p, intent, flags); } IntentStarted(Parcel p) { super(p); - intent = Intent.CREATOR.createFromParcel(p); + intent = IntentProtoParcelable.create(p); } } @@ -232,8 +231,7 @@ public abstract class AppLaunchEvent implements Parcelable { } public static class ActivityLaunchCancelled extends AppLaunchEvent { - public final @Nullable - @ActivityRecordProto byte[] activityRecordSnapshot; + public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot; public ActivityLaunchCancelled(@SequenceId long sequenceId, @Nullable @ActivityRecordProto byte[] snapshot) { @@ -352,7 +350,6 @@ public abstract class AppLaunchEvent implements Parcelable { ActivityLaunchCancelled.class, }; - // TODO: move to @ActivityRecordProto byte[] once we have unit tests. public static class ActivityRecordProtoParcelable { public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, int flags) { @@ -365,4 +362,31 @@ public abstract class AppLaunchEvent implements Parcelable { return data; } } + + public static class IntentProtoParcelable { + private static final int INTENT_PROTO_CHUNK_SIZE = 1024; + + public static void write(Parcel p, @NonNull Intent intent, int flags) { + // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, + // so create a new one every time. + final ProtoOutputStream protoOutputStream = + new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE); + // Write this data out as the top-most IntentProto (i.e. it is not a sub-object). + intent.writeToProto(protoOutputStream); + final byte[] bytes = protoOutputStream.getBytes(); + + p.writeByteArray(bytes); + } + + // TODO: Should be mockable for testing? + // We cannot deserialize in the platform because we don't have a 'readFromProto' + // code. + public static @NonNull Intent create(Parcel p) { + // This will "read" the correct amount of data, but then we discard it. + byte[] data = p.createByteArray(); + + // Never called by real code in a platform, this binder API is implemented only in C++. + return new Intent("<cannot deserialize IntentProto>"); + } + } } diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index 7fcad360b8fe..9a30b35f02a2 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -24,12 +24,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Handler; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityMetricsLaunchObserver; @@ -43,10 +47,20 @@ import com.android.server.wm.ActivityTaskManagerInternal; */ public class IorapForwardingService extends SystemService { - public static final boolean DEBUG = true; // TODO: read from a getprop? public static final String TAG = "IorapForwardingService"; + /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */ + public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + /** $> adb shell 'setprop iorapd.enable true' */ + private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true); + /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ + private static boolean WTF_CRASH = SystemProperties.getBoolean( + "iorapd.forwarding_service.wtf_crash", false); private IIorap mIorapRemote; + private final Object mLock = new Object(); + /** Handle onBinderDeath by periodically trying to reconnect. */ + private final Handler mHandler = + new BinderConnectionHandler(IoThread.getHandler().getLooper()); /** * Initializes the system service. @@ -58,7 +72,7 @@ public class IorapForwardingService extends SystemService { * @param context The system server context. */ public IorapForwardingService(Context context) { - super(context); + super(context); } //<editor-fold desc="Providers"> @@ -78,12 +92,40 @@ public class IorapForwardingService extends SystemService { @VisibleForTesting protected IIorap provideIorapRemote() { + IIorap iorap; try { - return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); + iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); } catch (ServiceManager.ServiceNotFoundException e) { - // TODO: how do we handle service being missing? - throw new AssertionError(e); + handleRemoteError(e); + return null; } + + try { + iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); + } catch (RemoteException e) { + handleRemoteError(e); + return null; + } + + return iorap; + } + + @VisibleForTesting + protected DeathRecipient provideDeathRecipient() { + return new DeathRecipient() { + @Override + public void binderDied() { + Log.w(TAG, "iorapd has died"); + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + }; + } + + @VisibleForTesting + protected boolean isIorapEnabled() { + // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process + // never comes up, so all binder connections will fail indefinitely. + return IS_ENABLED; } //</editor-fold> @@ -94,15 +136,128 @@ public class IorapForwardingService extends SystemService { Log.v(TAG, "onStart"); } + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + + private class BinderConnectionHandler extends Handler { + public BinderConnectionHandler(android.os.Looper looper) { + super(looper); + } + + public static final int MESSAGE_BINDER_CONNECT = 0; + + private int mAttempts = 0; + + @Override + public void handleMessage(android.os.Message message) { + switch (message.what) { + case MESSAGE_BINDER_CONNECT: + if (!retryConnectToRemoteAndConfigure(mAttempts)) { + mAttempts++; + } else { + mAttempts = 0; + } + break; + default: + throw new AssertionError("Unknown message: " + message.toString()); + } + } + } + + /** + * Handle iorapd shutdowns and crashes, by attempting to reconnect + * until the service is reached again. + * + * <p>The first connection attempt is synchronous, + * subsequent attempts are done by posting delayed tasks to the IoThread.</p> + * + * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. + */ + private boolean retryConnectToRemoteAndConfigure(int attempts) { + final int sleepTime = 1000; // ms + + if (DEBUG) { + Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); + } + + if (connectToRemoteAndConfigure()) { + return true; + } + + // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually + // called 'adb shell stop iorapd' , which means this would loop until it comes back + // up. + // + // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid + // printing this warning. + Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); + + // Use a handler instead of Thread#sleep to avoid backing up the binder thread + // when this is called from the death recipient callback. + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), + sleepTime); + + return false; + + // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); + } + + private boolean connectToRemoteAndConfigure() { + synchronized (mLock) { + // Synchronize against any concurrent calls to this via the DeathRecipient. + return connectToRemoteAndConfigureLocked(); + } + } + + private boolean connectToRemoteAndConfigureLocked() { + if (!isIorapEnabled()) { + if (DEBUG) { + Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); + } + // When we see that iorapd is disabled (when system server comes up), + // it stays disabled permanently until the next system server reset. + + // TODO: consider listening to property changes as a callback, then we can + // be more dynamic about handling enable/disable. + return true; + } + // Connect to the native binder service. mIorapRemote = provideIorapRemote(); + if (mIorapRemote == null) { + Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); + return false; + } invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) ); + registerInProcessListenersLocked(); + + return true; + } + + private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); + private boolean mRegisteredListeners = false; + + private void registerInProcessListenersLocked() { + if (mRegisteredListeners) { + // Listeners are registered only once (idempotent operation). + // + // Today listeners are tolerant of the remote side going away + // by handling remote errors. + // + // We could try to 'unregister' the listener when we get a binder disconnect, + // but we'd still have to handle the case of encountering synchronous errors so + // it really wouldn't be a win (other than having less log spew). + return; + } // Listen to App Launch Sequence events from ActivityTaskManager, // and forward them to the native binder service. ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); - launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver()); + launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); + + mRegisteredListeners = true; } private class AppLaunchObserver implements ActivityMetricsLaunchObserver { @@ -110,6 +265,8 @@ public class IorapForwardingService extends SystemService { // launch sequences on the native side. private @AppLaunchEvent.SequenceId long mSequenceId = -1; + // All callbacks occur on the same background thread. Don't synchronize explicitly. + @Override public void onIntentStarted(@NonNull Intent intent) { // #onIntentStarted [is the only transition that] initiates a new launch sequence. @@ -174,7 +331,7 @@ public class IorapForwardingService extends SystemService { invokeRemote(() -> mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity)) + new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity)) ); } } @@ -201,6 +358,7 @@ public class IorapForwardingService extends SystemService { } } + /** Allow passing lambdas to #invokeRemote */ private interface RemoteRunnable { void run() throws RemoteException; } @@ -209,8 +367,26 @@ public class IorapForwardingService extends SystemService { try { r.run(); } catch (RemoteException e) { - // TODO: what do we do with exceptions? - throw new AssertionError("not implemented", e); + // This could be a logic error (remote side returning error), which we need to fix. + // + // This could also be a DeadObjectException in which case its probably just iorapd + // being manually restarted. + // + // Don't make any assumption, since DeadObjectException could also mean iorapd crashed + // unexpectedly. + // + // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. + handleRemoteError(e); } } + + private static void handleRemoteError(Throwable t) { + if (WTF_CRASH) { + // In development modes, we just want to crash. + throw new AssertionError("unexpected remote error", t); + } else { + // Log to wtf which gets sent to dropbox, and in system_server this does not crash. + Log.wtf(TAG, t); + } + } } diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml index f83a16ec0916..919154d3e48a 100644 --- a/startop/iorap/tests/AndroidTest.xml +++ b/startop/iorap/tests/AndroidTest.xml @@ -33,6 +33,15 @@ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"> </target_preparer> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- Crash instead of using Log.wtf within the system_server iorap code. --> + <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" /> + <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status --> + <option name="set-property" key="iorapd.binder.fake" value="true" /> + <option name="restore-properties" value="true" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.google.android.startop.iorap.tests" /> <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index 826ad82dfbb2..818ebd998f50 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.app.Service; @@ -32,6 +33,9 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This service can be implemented by the default dialer (see * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow @@ -88,6 +92,128 @@ import com.android.internal.telecom.ICallScreeningService; * </pre> */ public abstract class CallScreeningService extends Service { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "CALL_DURATION_" }, + value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM, + CALL_DURATION_LONG}) + public @interface CallDuration {} + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was < 3 seconds in duration. + */ + public static final int CALL_DURATION_VERY_SHORT = 1; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration. + */ + public static final int CALL_DURATION_SHORT = 2; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration. + */ + public static final int CALL_DURATION_MEDIUM = 3; + + /** + * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the + * {@link CallScreeningService} the duration of a call for which the user reported the nuisance + * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The + * {@link CallScreeningService} can use this as a signal for training nuisance detection + * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of + * identifying call log information to the {@link CallScreeningService}. + * <p> + * Indicates the call was greater than 120 seconds. + */ + public static final int CALL_DURATION_LONG = 4; + + /** + * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to + * fill the call screening role when the user indicates through the default dialer whether a + * call is a nuisance call or not (see + * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). + * <p> + * The following extra values are provided for the call: + * <ol> + * <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li> + * <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance + * call, {@code false} otherwise.</li> + * <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed, + * blocked).</li> + * <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see + * {@link #EXTRA_CALL_DURATION} for valid values).</li> + * </ol> + * <p> + * {@link CallScreeningService} implementations which want to track whether the user reports + * calls are nuisance calls should use declare a broadcast receiver in their manifest for this + * intent. + * <p> + * Note: Only {@link CallScreeningService} implementations which have provided + * {@link CallIdentification} information for calls at some point will receive this intent. + */ + public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = + "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; + + /** + * Extra used to provide the handle of the call for + * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. The call handle is reported as a + * {@link Uri}. + */ + public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; + + /** + * Boolean extra used to indicate whether the user reported a call as a nuisance call. + * When {@code true}, the user reported that a call was a nuisance call, {@code false} + * otherwise. Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. + */ + public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; + + /** + * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of + * call. Valid values are: + * <UL> + * <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li> + * <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li> + * </UL> + */ + public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; + + /** + * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long + * the call lasted. Valid values are: + * <UL> + * <LI>{@link #CALL_DURATION_VERY_SHORT}</LI> + * <LI>{@link #CALL_DURATION_SHORT}</LI> + * <LI>{@link #CALL_DURATION_MEDIUM}</LI> + * <LI>{@link #CALL_DURATION_LONG}</LI> + * </UL> + */ + public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; + /** * The {@link Intent} that must be declared as handled by the service. */ diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 0fe5e080d1f8..12a534418663 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1970,6 +1970,33 @@ public class TelecomManager { } /** + * Called by the default dialer to report to Telecom when the user has marked a previous + * incoming call as a nuisance call or not. + * <p> + * Where the user has chosen a {@link CallScreeningService} to fill the call screening role, + * Telecom will notify that {@link CallScreeningService} of the user's report. + * <p> + * Requires that the caller is the default dialer app. + * + * @param handle The phone number of an incoming call which the user is reporting as either a + * nuisance of non-nuisance call. + * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call, + * {@code false} if the user is reporting the call as a non-nuisance call. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.reportNuisanceCallStatus(handle, isNuisanceCall, + mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); + } + } + } + + /** * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity. * @param intent The {@link Intent#ACTION_CALL} intent to handle. * @hide diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index e1d5c17d5e3a..5030f90afd3e 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -285,6 +285,8 @@ interface ITelecomService { */ boolean isInEmergencyCall(); + oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage); + /** * @see TelecomServiceImpl#handleCallIntent */ @@ -299,4 +301,5 @@ interface ITelecomService { void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); void setTestAutoModeApp(String packageName); + } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 15abdb78b214..548227067b67 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2375,6 +2375,9 @@ public final class Telephony { /** * Contains message parts. + * + * To avoid issues where applications might cache a part ID, the ID of a deleted part must + * not be reused to point at a new part. */ public static final class Part implements BaseColumns { @@ -2386,6 +2389,12 @@ public final class Telephony { } /** + * The {@code content://} style URL for this table. Can be appended with a part ID to + * address individual parts. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part"); + + /** * The identifier of the message which this part belongs to. * <P>Type: INTEGER</P> */ diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index 2b99ce1d8252..2d29875aadb4 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -50,10 +50,10 @@ public class CallAttributes implements Parcelable { } private CallAttributes(Parcel in) { - mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass() - .getClassLoader()); + mPreciseCallState = (PreciseCallState) + in.readValue(PreciseCallState.class.getClassLoader()); mNetworkType = in.readInt(); - mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader()); + mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader()); } // getters diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 312b31899ad6..349880d5ea3c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -608,11 +608,41 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL = "carrier_promote_wfc_on_call_fail_bool"; - /** Flag specifying whether provisioning is required for VOLTE. */ + /** + * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi + * Calling. + */ public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; /** + * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning + * before it can be set as enabled. + * + * If true, the UT capability will be set to false for the newly loaded subscription + * and will require the carrier provisioning app to set the persistent provisioning result. + * If false, the platform will not wait for provisioning status updates for the UT capability + * and enable the UT over IMS capability for the subscription when the subscription is loaded. + * + * The default value for this key is {@code false}. + */ + public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = + "carrier_ut_provisioning_required_bool"; + + /** + * Flag indicating whether or not the carrier supports Supplementary Services over the UT + * interface for this subscription. + * + * If true, the device will use Supplementary Services over UT when provisioned (see + * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to + * circuit switch for supplementary services and will disable this capability for IMS entirely. + * + * The default value for this key is {@code true}. + */ + public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = + "carrier_supports_ss_over_ut_bool"; + + /** * Flag specifying if WFC provisioning depends on VoLTE provisioning. * * {@code false}: default value; honor actual WFC provisioning state. @@ -2508,10 +2538,18 @@ public class CarrierConfigManager { public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock"; /** - * SUPL NI emergency extension time in seconds. Default to "0". + * Control Plane / SUPL NI emergency extension time in seconds. Default to "0". */ public static final String KEY_ES_EXTENSION_SEC = KEY_PREFIX + "es_extension_sec"; + /** + * Space separated list of Android package names of proxy applications representing + * the non-framework entities requesting location directly from GNSS without involving + * the framework, as managed by IGnssVisibilityControl.hal. For example, + * "com.example.mdt com.example.ims". + */ + public static final String KEY_NFW_PROXY_APPS = KEY_PREFIX + "nfw_proxy_apps"; + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true); @@ -2525,6 +2563,7 @@ public class CarrierConfigManager { defaults.putString(KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, "0"); defaults.putString(KEY_GPS_LOCK_STRING, "3"); defaults.putString(KEY_ES_EXTENSION_SEC, "0"); + defaults.putString(KEY_NFW_PROXY_APPS, ""); return defaults; } } @@ -2566,6 +2605,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 7b29f69da6da..30e641d61143 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -148,8 +148,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P /** * Return the Bit Error Rate - * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE. - * @hide + * + * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}. */ public int getBitErrorRate() { return mBitErrorRate; diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index ad3ca6d129c9..91375bc2f11e 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -292,14 +292,27 @@ public class SignalStrength implements Parcelable { * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 * * @return RSSI in ASU 0..31, 99, or UNAVAILABLE + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getAsuLevel}. + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths */ + @Deprecated public int getGsmSignalStrength() { return mGsm.getAsuLevel(); } /** * Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getBitErrorRate}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getGsmBitErrorRate() { return mGsm.getBitErrorRate(); } @@ -308,14 +321,28 @@ public class SignalStrength implements Parcelable { * Get the CDMA RSSI value in dBm * * @return the CDMA RSSI value or {@link #INVALID} if invalid + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getCdmaDbm}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getCdmaDbm() { return mCdma.getCdmaDbm(); } /** * Get the CDMA Ec/Io value in dB*10 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getCdmaEcio}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getCdmaEcio() { return mCdma.getCdmaEcio(); } @@ -324,51 +351,112 @@ public class SignalStrength implements Parcelable { * Get the EVDO RSSI value in dBm * * @return the EVDO RSSI value or {@link #INVALID} if invalid + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoDbm}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoDbm() { return mCdma.getEvdoDbm(); } /** * Get the EVDO Ec/Io value in dB*10 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoEcio}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoEcio() { return mCdma.getEvdoEcio(); } /** * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoSnr}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoSnr() { return mCdma.getEvdoSnr(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRssi}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteSignalStrength() { return mLte.getRssi(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRsrp}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRsrp() { return mLte.getRsrp(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRsrq}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRsrq() { return mLte.getRsrq(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRssnr}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRssnr() { return mLte.getRssnr(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getCqi}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteCqi() { return mLte.getCqi(); } @@ -391,11 +479,17 @@ public class SignalStrength implements Parcelable { } /** - * Get the signal level as an asu value between 0..31, 99 is unknown + * Get the signal level as an asu value with a range dependent on the underlying technology. * + * @deprecated this information should be retrieved from + * {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology, + * this method is misleading and should not be used. + * @see android.telephony#CellSignalStrength + * @see android.telephony.SignalStrength#getCellSignalStrengths * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getAsuLevel() { return getPrimary().getAsuLevel(); } @@ -403,9 +497,15 @@ public class SignalStrength implements Parcelable { /** * Get the signal strength as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrength#getDbm()}. Because the levels vary by technology, + * this method is misleading and should not be used. + * @see android.telephony#CellSignalStrength + * @see android.telephony.SignalStrength#getCellSignalStrengths * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getDbm() { return getPrimary().getDbm(); } @@ -413,9 +513,15 @@ public class SignalStrength implements Parcelable { /** * Get Gsm signal strength as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getDbm}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmDbm() { return mGsm.getDbm(); } @@ -423,9 +529,15 @@ public class SignalStrength implements Parcelable { /** * Get gsm as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getLevel}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmLevel() { return mGsm.getLevel(); } @@ -433,9 +545,15 @@ public class SignalStrength implements Parcelable { /** * Get the gsm signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmAsuLevel() { return mGsm.getAsuLevel(); } @@ -443,9 +561,15 @@ public class SignalStrength implements Parcelable { /** * Get cdma as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getLevel}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getCdmaLevel() { return mCdma.getLevel(); } @@ -453,9 +577,17 @@ public class SignalStrength implements Parcelable { /** * Get the cdma signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of + * ASU for CDMA, the resultant value is Android-specific and is not recommended + * for use. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getCdmaAsuLevel() { return mCdma.getAsuLevel(); } @@ -463,9 +595,15 @@ public class SignalStrength implements Parcelable { /** * Get Evdo as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoLevel}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getEvdoLevel() { return mCdma.getEvdoLevel(); } @@ -473,9 +611,17 @@ public class SignalStrength implements Parcelable { /** * Get the evdo signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of + * ASU for EvDO, the resultant value is Android-specific and is not recommended + * for use. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getEvdoAsuLevel() { return mCdma.getEvdoAsuLevel(); } @@ -483,9 +629,15 @@ public class SignalStrength implements Parcelable { /** * Get LTE as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getDbm}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteDbm() { return mLte.getRsrp(); } @@ -493,9 +645,15 @@ public class SignalStrength implements Parcelable { /** * Get LTE as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getLevel}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteLevel() { return mLte.getLevel(); } @@ -504,26 +662,46 @@ public class SignalStrength implements Parcelable { * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteAsuLevel() { return mLte.getAsuLevel(); } /** * @return true if this is for GSM + * + * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in + * this SignalStrength report or if the report contains no valid SignalStrength + * information. Instead callers should use + * {@link android.telephony.SignalStrength#getCellSignalStrengths + * getCellSignalStrengths()} to determine which types of information are contained + * in the SignalStrength report. */ + @Deprecated public boolean isGsm() { return !(getPrimary() instanceof CellSignalStrengthCdma); } /** - * @return get TD_SCDMA dbm + * @return get TD-SCDMA dBm + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getDbm}. * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaDbm() { return mTdscdma.getRscp(); } @@ -534,9 +712,15 @@ public class SignalStrength implements Parcelable { * INT_MAX: 0x7FFFFFFF denotes invalid value * Reference: 3GPP TS 25.123, section 9.1.1.1 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getLevel}. + * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaLevel() { return mTdscdma.getLevel(); } @@ -544,18 +728,30 @@ public class SignalStrength implements Parcelable { /** * Get the TD-SCDMA signal level as an asu value. * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaAsuLevel() { return mTdscdma.getAsuLevel(); } /** - * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69. + * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getRscp}. * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaRscp() { return mWcdma.getRscp(); } @@ -563,8 +759,14 @@ public class SignalStrength implements Parcelable { /** * Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaAsuLevel() { /* * 3GPP 27.007 (Ver 10.3.0) Sec 8.69 @@ -578,10 +780,16 @@ public class SignalStrength implements Parcelable { } /** - * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69. + * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getDbm}. * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaDbm() { return mWcdma.getDbm(); } @@ -589,13 +797,19 @@ public class SignalStrength implements Parcelable { /** * Get WCDMA as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getDbm}. + * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaLevel() { return mWcdma.getLevel(); } - /** + /** * @return hash code */ @Override @@ -639,9 +853,13 @@ public class SignalStrength implements Parcelable { * Set SignalStrength based on intent notifier map * * @param m intent notifier map + * + * @deprecated this method relies on non-stable implementation details, and full access to + * internal storage is available via {@link getCellSignalStrengths()}. * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private void setFromNotifierBundle(Bundle m) { mCdma = m.getParcelable("Cdma"); mGsm = m.getParcelable("Gsm"); @@ -654,9 +872,13 @@ public class SignalStrength implements Parcelable { * Set intent notifier Bundle based on SignalStrength * * @param m intent notifier Bundle + * + * @deprecated this method relies on non-stable implementation details, and full access to + * internal storage is available via {@link getCellSignalStrengths()}. * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public void fillInNotifierBundle(Bundle m) { m.putParcelable("Cdma", mCdma); m.putParcelable("Gsm", mGsm); diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 1378bb004696..aaca18a0d904 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -16,12 +16,18 @@ package android.telephony; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMapClient; +import android.bluetooth.BluetoothProfile; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; @@ -32,6 +38,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telecom.PhoneAccount; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,6 +68,8 @@ import java.util.Map; */ public final class SmsManager { private static final String TAG = "SmsManager"; + private static final boolean DBG = false; + /** * A psuedo-subId that represents the default subId at any given time. The actual subId it * represents changes as the default subId is changed. @@ -339,6 +348,44 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message body"); } + // A Manager code accessing another manager is *not* acceptable, in Android. + // In this particular case, it is unavoidable because of the following: + // If the subscription for this SmsManager instance belongs to a remote SIM + // then a listener to get BluetoothMapClient proxy needs to be started up. + // Doing that is possible only in a foreground thread or as a system user. + // i.e., Can't be done in ISms service. + // For that reason, SubscriptionManager needs to be accessed here to determine + // if the subscription belongs to a remote SIM. + // Ideally, there should be another API in ISms to service messages going thru + // remote SIM subscriptions (and ISms should be tweaked to be able to access + // BluetoothMapClient proxy) + Context context = ActivityThread.currentApplication().getApplicationContext(); + SubscriptionManager manager = (SubscriptionManager) context + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int subId = getSubscriptionId(); + SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId); + if (DBG) { + Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info); + } + if (info == null) { + // There is no subscription for the given subId. That can only mean one thing: + // the caller is using a SmsManager instance with an obsolete subscription id. + // That is most probably because caller didn't invalidate SmsManager instance + // for an already deleted subscription id. + Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete."); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + } + + /* If the Subscription associated with this SmsManager instance belongs to a remote-sim, + * then send the message thru the remote-sim subscription. + */ + if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { + if (DBG) Log.d(TAG, "sending message thru bluetooth"); + sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent, + deliveryIntent, info); + return; + } + try { ISms iccISms = getISmsServiceOrThrow(); iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), @@ -350,6 +397,79 @@ public final class SmsManager { } } + private void sendTextMessageBluetooth(String destAddr, String scAddress, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + SubscriptionInfo info) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + // No bluetooth service on this platform? + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId()); + if (device == null) { + if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId()); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(), + new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent), + BluetoothProfile.MAP_CLIENT); + } + + private class MapMessageSender implements BluetoothProfile.ServiceListener { + final Uri[] mDestAddr; + private String mMessage; + final BluetoothDevice mDevice; + final PendingIntent mSentIntent; + final PendingIntent mDeliveryIntent; + MapMessageSender(final String destAddr, final String message, final BluetoothDevice device, + final PendingIntent sentIntent, final PendingIntent deliveryIntent) { + super(); + mDestAddr = new Uri[] {new Uri.Builder() + .appendPath(destAddr) + .scheme(PhoneAccount.SCHEME_TEL) + .build()}; + mMessage = message; + mDevice = device; + mSentIntent = sentIntent; + mDeliveryIntent = deliveryIntent; + } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DBG) Log.d(TAG, "Service connected"); + if (profile != BluetoothProfile.MAP_CLIENT) return; + BluetoothMapClient mapProfile = (BluetoothMapClient) proxy; + if (mMessage != null) { + if (DBG) Log.d(TAG, "Sending message thru bluetooth"); + mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent); + mMessage = null; + } + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile); + } + + @Override + public void onServiceDisconnected(int profile) { + if (mMessage != null) { + if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message"); + sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + mMessage = null; + } + } + } + + private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) { + try { + intent.send(errorCode); + } catch (PendingIntent.CanceledException e) { + // PendingIntent is cancelled. ignore sending this error code back to + // caller. + if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage()); + } + } + /** * Send a text based SMS without writing it into the SMS Provider. * @@ -888,8 +1008,6 @@ public final class SmsManager { } } - - /** * Get the SmsManager associated with the default subscription id. The instance will always be * associated with the default subscription id, even if the default subscription id is changed. @@ -2029,6 +2147,79 @@ public final class SmsManager { } /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The prefixes is a list of prefix {@code String} separated by this delimiter. + * @hide + */ + public static final String REGEX_PREFIX_DELIMITER = ","; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The success status to be added into the intent to be sent to the calling package. + * @hide + */ + public static final int RESULT_STATUS_SUCCESS = 0; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The timeout status to be added into the intent to be sent to the calling package. + * @hide + */ + public static final int RESULT_STATUS_TIMEOUT = 1; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * Intent extra key of the retrieved SMS message as a {@code String}. + * @hide + */ + public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE"; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * Intent extra key of SMS retriever status, which indicates whether the request for the + * coming SMS message is SUCCESS or TIMEOUT + * @hide + */ + public static final String EXTRA_STATUS = "android.telephony.extra.STATUS"; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int} + * @hide + */ + public static final String EXTRA_SIM_SUBSCRIPTION_ID = + "android.telephony.extra.SIM_SUBSCRIPTION_ID"; + + /** + * Create a single use app specific incoming SMS request for the calling package. + * + * This method returns a token that if included in a subsequent incoming SMS message, and the + * SMS message has a prefix from the given prefixes list, the provided {@code intent} will be + * sent with the SMS data to the calling package. + * + * The token is only good for one use within a reasonable amount of time. After an SMS has been + * received containing the token all subsequent SMS messages with the token will be routed as + * normal. + * + * An app can only have one request at a time, if the app already has a request pending it will + * be replaced with a new request. + * + * @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The + * matching SMS message should have at least one of the prefixes in the beginning of the + * message. + * @param intent this intent is sent when the matching SMS message is received. + * @return Token to include in an SMS message. + */ + @Nullable + public String createAppSpecificSmsTokenWithPackageInfo( + @Nullable String prefixes, @NonNull PendingIntent intent) { + try { + ISms iccSms = getISmsServiceOrThrow(); + return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(), + ActivityThread.currentPackageName(), prefixes, intent); + + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + return null; + } + } + + /** * Filters a bundle to only contain MMS config variables. * * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 51d5ab17ee16..a3b3374b183c 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -184,6 +184,11 @@ public class SubscriptionInfo implements Parcelable { private int mProfileClass; /** + * Type of subscription + */ + private int mSubscriptionType; + + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, @@ -206,7 +211,8 @@ public class SubscriptionInfo implements Parcelable { @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass); + isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass, + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); } /** @@ -217,7 +223,7 @@ public class SubscriptionInfo implements Parcelable { Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, - boolean isGroupDisabled, int carrierid, int profileClass) { + boolean isGroupDisabled, int carrierId, int profileClass, int subType) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -239,11 +245,11 @@ public class SubscriptionInfo implements Parcelable { this.mGroupUUID = groupUUID; this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; - this.mCarrierId = carrierid; + this.mCarrierId = carrierId; this.mProfileClass = profileClass; + this.mSubscriptionType = subType; } - /** * @return the subscription ID. */ @@ -487,6 +493,16 @@ public class SubscriptionInfo implements Parcelable { } /** + * This method returns the type of a subscription. It can be + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @return the type of subscription + */ + public @SubscriptionManager.SubscriptionType int getSubscriptionType() { + return mSubscriptionType; + } + + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} * returns true). @@ -612,11 +628,12 @@ public class SubscriptionInfo implements Parcelable { boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); int profileClass = source.readInt(); + int subType = source.readInt(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid, profileClass); + isMetered, isGroupDisabled, carrierid, profileClass, subType); } @Override @@ -650,6 +667,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); + dest.writeInt(mSubscriptionType); } @Override @@ -686,7 +704,8 @@ public class SubscriptionInfo implements Parcelable { + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled - + " profileClass=" + mProfileClass + "}"; + + " profileClass=" + mProfileClass + + " subscriptionType=" + mSubscriptionType + "}"; } @Override diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 9fa4c3ce899f..869cf1cf9e14 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -247,7 +247,9 @@ public class SubscriptionManager { public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; /** - * TelephonyProvider column name for SIM ICC Identifier + * TelephonyProvider column name for a unique identifier for the subscription within the + * specific subscription type. For example, it contains SIM ICC Identifier subscriptions + * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices. * <P>Type: TEXT (String)</P> */ /** @hide */ @@ -265,6 +267,63 @@ public class SubscriptionManager { public static final int SIM_NOT_INSERTED = -1; /** + * The slot-index for Bluetooth Remote-SIM subscriptions + * @hide + */ + public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX; + + /** + * TelephonyProvider column name Subscription-type. + * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions, + * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions. + * Default value is 0. + */ + /** @hide */ + public static final String SUBSCRIPTION_TYPE = "subscription_type"; + + /** + * This constant is to designate a subscription as a Local-SIM Subscription. + * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the + * device. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + + /** + * This constant is to designate a subscription as a Remote-SIM Subscription. + * <p> + * A Remote-SIM subscription is for a SIM on a phone connected to this device via some + * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can + * be used for SMS, Voice and data by proxying data through the connected device. + * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs. + * </p> + * + * <p> + * A Remote-SIM is available only as long the phone stays connected to this device. + * When the phone disconnects, Remote-SIM subscription is removed from this device and is + * no longer known. All data associated with the subscription, such as stored SMS, call logs, + * contacts etc, are removed from this device. + * </p> + * + * <p> + * If the phone re-connects to this device, a new Remote-SIM subscription is created for + * the phone. The Subscription Id associated with the new subscription is different from + * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the + * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that + * was never seen before. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, + value = { + SUBSCRIPTION_TYPE_LOCAL_SIM, + SUBSCRIPTION_TYPE_REMOTE_SIM}) + public @interface SubscriptionType {} + + /** * TelephonyProvider column name for user displayed name. * <P>Type: TEXT (String)</P> */ @@ -1145,7 +1204,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted + * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} @@ -1427,21 +1486,84 @@ public class SubscriptionManager { logd("[addSubscriptionInfoRecord]- invalid slotIndex"); } + addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM); + + // FIXME: Always returns null? + return null; + + } + + /** + * Add a new SubscriptionInfo to SubscriptionInfo database if needed + * @param uniqueId This is the unique identifier for the subscription within the + * specific subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType + * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex, + int subscriptionType) { + if (VDBG) { + logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", displayName:" + displayName + ", slotIndex:" + slotIndex + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - // FIXME: This returns 1 on success, 0 on error should should we return it? - iSub.addSubInfoRecord(iccId, slotIndex); + if (iSub == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result); } else { - logd("[addSubscriptionInfoRecord]- ISub service is null"); + logd("successfully added new subscription"); } } catch (RemoteException ex) { // ignore it } + } - // FIXME: Always returns null? - return null; + /** + * Remove SubscriptionInfo record from the SubscriptionInfo database + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) { + if (VDBG) { + logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub == null) { + Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.removeSubInfo(uniqueId, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result); + } else { + logd("successfully removed subscription"); + } + } catch (RemoteException ex) { + // ignore it + } } /** @@ -2737,6 +2859,95 @@ public class SubscriptionManager { } } + /** + * Enabled or disable a subscription. This is currently used in the settings page. + * + * <p> + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * + * @param enable whether user is turning it on or off. + * @param subscriptionId Subscription to be enabled or disabled. + * It could be a eSIM or pSIM subscription. + * + * @return whether the operation is successful. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) { + if (VDBG) { + logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable); + } + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.setSubscriptionEnabled(enable, subscriptionId); + } + } catch (RemoteException ex) { + // ignore it + } + + return false; + } + + /** + * Returns whether the subscription is enabled or not. This is different from activated + * or deactivated for two aspects. 1) For when user disables a physical subscription, we + * actually disable the modem because we can't switch off the subscription. 2) For eSIM, + * user may enable one subscription but the system may activate another temporarily. In this + * case, user enabled one is different from current active one. + + * @param subscriptionId The subscription it asks about. + * @return whether it's enabled or not. {@code true} if user set this subscription enabled + * earlier, or user never set subscription enable / disable on this slot explicitly, and + * this subscription is currently active. Otherwise, it returns {@code false}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isSubscriptionEnabled(int subscriptionId) { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.isSubscriptionEnabled(subscriptionId); + } + } catch (RemoteException ex) { + // ignore it + } + + return false; + } + + /** + * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)} + * for more details. + * + * @param slotIndex which slot it asks about. + * @return which subscription is enabled on this slot. If there's no enabled subscription + * in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public int getEnabledSubscriptionId(int slotIndex) { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getEnabledSubscriptionId(slotIndex); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getEnabledSubscriptionId, subId = " + subId); + return subId; + } + private interface CallISubMethodHelper { int callMethod(ISub iSub) throws RemoteException; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 80533535017e..98fc7251e9a8 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -93,6 +93,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.Executor; import java.util.regex.Matcher; @@ -6847,13 +6848,13 @@ public class TelephonyManager { /** * Values used to return status for hasCarrierPrivileges call. */ - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; /** @@ -7055,6 +7056,7 @@ public class TelephonyManager { /** @hide */ @SystemApi + @TestApi @SuppressLint("Doclava125") public int checkCarrierPrivilegesForPackage(String pkgName) { try { @@ -8585,18 +8587,47 @@ public class TelephonyManager { } - /** @hide */ + /** + * Returns a well-formed IETF BCP 47 language tag representing the locale from the SIM, e.g, + * en-US. Returns {@code null} if no locale could be derived from subscriptions. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * + * @see Locale#toLanguageTag() + * @see Locale#forLanguageTag(String) + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @Nullable public String getSimLocale() { + try { + final ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getSimLocaleForSubscriber(getSubId()); + } + } catch (RemoteException ex) { + } + return null; + } + + /** + * TODO delete after SuW migrates to new API. + * @hide + */ public String getLocaleFromDefaultSim() { try { final ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getLocaleFromDefaultSim(); + return telephony.getSimLocaleForSubscriber(getSubId()); } } catch (RemoteException ex) { } return null; } + /** * Requests the modem activity info. The recipient will place the result * in `result`. diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 0e5c71d6ef90..bca088e618c0 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -91,20 +91,6 @@ public class EuiccManager { "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE"; /** - * Intent action to select a profile to enable before download a new eSIM profile. - * - * May be called during device provisioning when there are multiple slots having profiles on - * them. This Intent launches a screen for all the current existing profiles and let users to - * choose which one they want to enable. In this case, the slot contains the profile will be - * activated. - * - * @hide - */ - @SystemApi - public static final String ACTION_PROFILE_SELECTION = - "android.telephony.euicc.action.PROFILE_SELECTION"; - - /** * Intent action to provision an embedded subscription. * * <p>May be called during device provisioning to launch a screen to perform embedded SIM @@ -129,6 +115,66 @@ public class EuiccManager { "android.telephony.euicc.action.RESOLVE_ERROR"; /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_ENABLE_SUBSCRIPTION}. + * + * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}. + * + * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_SUBSCRIPTION_NICKNAME}. + * + * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action + * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience + * during this operation. The action is received by the Telephony framework, which in turn + * selects and launches an appropriate LPA activity to present UI to the user. For example, the + * activity may show a confirmation dialog, a progress dialog, or an error dialog when + * necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + + /** * Result code for an operation indicating that the operation succeeded. */ public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; @@ -219,6 +265,37 @@ public class EuiccManager { "android.telephony.euicc.extra.FORCE_PROVISION"; /** + * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED}, + * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and + * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_ID = + "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + + /** + * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean + * value of whether to enable or disable the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ENABLE_SUBSCRIPTION = + "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; + + /** + * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new + * nickname for the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_NICKNAME = + "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; + + /** * Optional meta-data attribute for a carrier app providing an icon to use to represent the * carrier. If not provided, the app's launcher icon will be used as a fallback. */ @@ -234,8 +311,8 @@ public class EuiccManager { @IntDef(prefix = {"EUICC_ACTIVATION_"}, value = { EUICC_ACTIVATION_TYPE_DEFAULT, EUICC_ACTIVATION_TYPE_BACKUP, - EUICC_ACTIVATION_TYPE_TRANSFER - + EUICC_ACTIVATION_TYPE_TRANSFER, + EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED, }) public @interface EuiccActivationType{} @@ -269,6 +346,14 @@ public class EuiccManager { @SystemApi public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; + /** + * The activation flow of eSIM requiring user account will be started. This can only be used + * when there is user account signed in. Otherwise, the flow will be failed. + * + * @hide + */ + @SystemApi + public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; /** * Euicc OTA update status which can be got by {@link #getOtaStatus} diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java new file mode 100644 index 000000000000..ac4d17a0ce65 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsException.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class defines an IMS-related exception that has been thrown while interacting with a + * device or carrier provided ImsService implementation. + * @hide + */ +@SystemApi +public class ImsException extends Exception { + + /** + * The operation has failed due to an unknown or unspecified error. + */ + public static final int CODE_ERROR_UNSPECIFIED = 0; + /** + * The operation has failed because there is no {@link ImsService} available to service it. This + * may be due to an {@link ImsService} crash or other illegal state. + * <p> + * This is a temporary error and the operation may be retried until the connection to the + * {@link ImsService} is restored. + */ + public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; + + /** + * This device or carrier configuration does not support IMS for this subscription. + * <p> + * This is a permanent configuration error and there should be no retry. + */ + public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CODE_ERROR_", value = { + CODE_ERROR_UNSPECIFIED, + CODE_ERROR_SERVICE_UNAVAILABLE, + CODE_ERROR_UNSUPPORTED_OPERATION + }) + public @interface ImsErrorCode {} + + private int mCode = CODE_ERROR_UNSPECIFIED; + + /** + * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message) { + super(getMessage(message, CODE_ERROR_UNSPECIFIED)); + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code) { + super(getMessage(message, code)); + mCode = code; + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a + * {@link Throwable} that contains the original error that was thrown to lead to this Exception. + * @param message an optional message to detail the error condition more specifically. + * @param cause the {@link Throwable} that caused this {@link ImsException} to be created. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) { + super(getMessage(message, code), cause); + mCode = code; + } + + /** + * @return the IMS Error code that is associated with this {@link ImsException}. + */ + public @ImsErrorCode int getCode() { + return mCode; + } + + private static String getMessage(String message, int code) { + StringBuilder builder; + if (!TextUtils.isEmpty(message)) { + builder = new StringBuilder(message); + builder.append(" (code: "); + builder.append(code); + builder.append(")"); + return builder.toString(); + } else { + return "code: " + code; + } + } +} diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 9414abd98b1c..eb99d5dcaaeb 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; * registration and MmTel capability status callbacks, as well as query/modify user settings for the * associated subscription. * - * @see #createForSubscriptionId(Context, int) + * @see #createForSubscriptionId(int) * @hide */ @SystemApi @@ -86,9 +86,7 @@ public class ImsMmTelManager { /** * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough. - * @hide */ - @SystemApi public static final int WIFI_MODE_WIFI_PREFERRED = 2; /** @@ -317,15 +315,12 @@ public class ImsMmTelManager { /** * Create an instance of ImsManager for the subscription id specified. * - * @param context The context to create this ImsMmTelManager instance within. * @param subId The ID of the subscription that this ImsMmTelManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ImsMmTelManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ImsMmTelManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -333,7 +328,7 @@ public class ImsMmTelManager { } /** - * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead. + * Only visible for testing, use {@link #createForSubscriptionId(int)} instead. * @hide */ @VisibleForTesting @@ -343,7 +338,7 @@ public class ImsMmTelManager { /** * Registers a {@link RegistrationCallback} with the system, which will provide registration - * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use + * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up. * @@ -356,13 +351,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@CallbackExecutor Executor executor, - @NonNull RegistrationCallback c) { + @NonNull RegistrationCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -374,6 +370,8 @@ public class ImsMmTelManager { getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -405,7 +403,7 @@ public class ImsMmTelManager { /** * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service * availability updates for the subscription specified in - * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)} + * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)} * can also be used to query this information at any time. * * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to @@ -421,13 +419,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull CapabilityCallback c) { + @NonNull CapabilityCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -439,6 +438,8 @@ public class ImsMmTelManager { getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -798,14 +799,6 @@ public class ImsMmTelManager { } } - private static SubscriptionManager getSubscriptionManager(Context context) { - SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); - if (manager == null) { - throw new RuntimeException("Could not find SubscriptionManager."); - } - return manager; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( ServiceManager.getService(Context.TELEPHONY_SERVICE)); diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index d37198a3e25d..b171f7940944 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,13 +21,17 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.WorkerThread; import android.content.Context; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsConfigImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ITelephony; @@ -38,13 +42,68 @@ import java.util.concurrent.Executor; * to changes in these configurations. * * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning - * applications and may vary. + * applications and may vary. For compatibility purposes, the first 100 integer values used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Some common constants have been defined in this + * class to make integrating with other system apps easier. USE WITH CARE! + * + * To avoid collisions, please use String based configurations when possible: + * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}. * @hide */ @SystemApi public class ProvisioningManager { /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. + */ + public static final String STRING_QUERY_RESULT_ERROR_GENERIC = + "STRING_QUERY_RESULT_ERROR_GENERIC"; + + /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the + * ImsService implementation was not ready for provisioning queries. + */ + public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = + "STRING_QUERY_RESULT_ERROR_NOT_READY"; + + /** + * The integer result of provisioning for the queried key is disabled. + */ + public static final int PROVISIONING_VALUE_DISABLED = 0; + + /** + * The integer result of provisioning for the queried key is enabled. + */ + public static final int PROVISIONING_VALUE_ENABLED = 1; + + + /** + * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in + * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning + * the subscription for WiFi Calling. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; + + /** + * Override the user-defined WiFi mode for this subscription, defined in + * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning + * this subscription for WiFi Calling. + * + * Valid values for this key are: + * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY}, + * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or + * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; + + /** * Callback for IMS provisioning changes. */ public static class Callback { @@ -113,15 +172,13 @@ public class ProvisioningManager { /** * Create a new {@link ProvisioningManager} for the subscription specified. - * @param context The context that this manager will use. + * * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ProvisioningManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ProvisioningManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -143,18 +200,21 @@ public class ProvisioningManager { * @see SubscriptionManager.OnSubscriptionsChangedListener * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or the subscription is invalid. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor, - @NonNull Callback callback) { + @NonNull Callback callback) throws ImsException { callback.setExecutor(executor); try { getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -180,10 +240,15 @@ public class ProvisioningManager { /** * Query for the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return an integer value for the provided key. + * @return an integer value for the provided key, or + * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int key) { try { @@ -195,10 +260,16 @@ public class ProvisioningManager { /** * Query for the String value associated with the provided key. - * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return a String value for the provided key, or {@code null} if the key doesn't exist. + * + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM. + * @return a String value for the provided key, {@code null} if the key doesn't exist, or one + * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC}, + * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int key) { try { @@ -210,10 +281,16 @@ public class ProvisioningManager { /** * Set the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * + * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined + * per OEM or carrier) when possible instead to avoid key collision if needed. * @param key An integer that represents the provisioning key, which is defined by the OEM. * @param value a integer value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) { try { @@ -226,10 +303,14 @@ public class ProvisioningManager { /** * Set the String value associated with the provided key. * - * @param key An integer that represents the provisioning key, which is defined by the OEM. + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM and + * should be appropriately namespaced to avoid collision. * @param value a String value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key, String value) { @@ -240,6 +321,57 @@ public class ProvisioningManager { } } + /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { + getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Get the provisioning status for the IMS MmTel capability specified. + * + * If provisioning is not required for the queried + * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and + * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will + * always return {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private static SubscriptionManager getSubscriptionManager(Context context) { SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); if (manager == null) { diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 7c793a5c18ac..1ee85633c6dc 100644 --- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -97,6 +97,13 @@ public final class CapabilityChangeRequest implements Parcelable { public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() { return radioTech; } + + @Override + public String toString() { + return "CapabilityPair{" + + "mCapability=" + mCapability + + ", radioTech=" + radioTech + '}'; + } } // Pair contains <radio tech, mCapability> @@ -212,6 +219,13 @@ public final class CapabilityChangeRequest implements Parcelable { } } + @Override + public String toString() { + return "CapabilityChangeRequest{" + + "mCapabilitiesToEnable=" + mCapabilitiesToEnable + + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}'; + } + /** * @hide */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 71a21743a449..4fc6a19d1f38 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -277,12 +277,14 @@ public class ImsConfig { * Wi-Fi calling roaming status. * Value is in Integer format. ON (1), OFF(0). */ - public static final int VOICE_OVER_WIFI_ROAMING = 26; + public static final int VOICE_OVER_WIFI_ROAMING = + ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE; /** * Wi-Fi calling modem - WfcModeFeatureValueConstants. * Value is in Integer format. */ - public static final int VOICE_OVER_WIFI_MODE = 27; + public static final int VOICE_OVER_WIFI_MODE = + ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE; /** * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0). * Value is in Integer format. diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index f35e88672a23..fea763ed5785 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -21,8 +21,10 @@ import android.telephony.ims.ImsReasonInfo; /** * This class defines a general IMS-related exception. * + * @deprecated Use {@link android.telephony.ims.ImsException} instead. * @hide */ +@Deprecated public class ImsException extends Exception { /** diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index a4eb424ab66e..58ebb159ea9f 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -560,4 +560,19 @@ interface ISms { * @param intent PendingIntent to be sent when an SMS is received containing the token. */ String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent); + + /** + * Create an app-only incoming SMS request for the calling package. + * + * If an incoming text contains the token returned by this method the provided + * <code>PendingIntent</code> will be sent containing the SMS data. + * + * @param subId the SIM id. + * @param callingPkg the package name of the calling app. + * @param prefixes the caller provided prefixes + * @param intent PendingIntent to be sent when a SMS is received containing the token and one + * of the prefixes + */ + String createAppSpecificSmsTokenWithPackageInfo( + int subId, String callingPkg, String prefixes, in PendingIntent intent); } diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java index 1cdf44d897b2..f2f2960e92a4 100644 --- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java +++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java @@ -188,4 +188,10 @@ public class ISmsImplBase extends ISms.Stub { public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) { throw new UnsupportedOperationException(); } + + @Override + public String createAppSpecificSmsTokenWithPackageInfo( + int subId, String callingPkg, String prefixes, PendingIntent intent) { + throw new UnsupportedOperationException(); + } } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 577ddbda50fa..a49d2d976d16 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -52,8 +52,8 @@ interface ISub { /** * Get the active SubscriptionInfo associated with the slotIndex * @param slotIndex the slot which the subscription is inserted - * @param callingPackage The package maing the call. - * @return SubscriptionInfo, maybe null if its not active + * @param callingPackage The package making the call. + * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex. */ SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage); @@ -115,6 +115,26 @@ interface ISub { int addSubInfoRecord(String iccId, int slotIndex); /** + * Add a new subscription info record, if needed + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this device + * @param subscriptionType the type of subscription to be added. + * @return 0 if success, < 0 on error. + */ + int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType); + + /** + * Remove subscription info record for the given device. + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the type of subscription to be removed + * @return 0 if success, < 0 on error. + */ + int removeSubInfo(String uniqueId, int subscriptionType); + + /** * Set SIM icon tint color by simInfo index * @param tint the icon tint color of the SIM * @param subId the unique SubscriptionInfo index in database @@ -256,6 +276,11 @@ interface ISub { String getSubscriptionProperty(int subId, String propKey, String callingPackage); + boolean setSubscriptionEnabled(boolean enable, int subId); + + boolean isSubscriptionEnabled(int subId); + + int getEnabledSubscriptionId(int slotIndex); /** * Get the SIM state for the slot index * @return SIM state as the ordinal of IccCardConstants.State diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 5736a465d449..8237d39c4c3d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1178,12 +1178,12 @@ interface ITelephony { void factoryReset(int subId); /** - * An estimate of the users's current locale based on the default SIM. + * Returns users's current locale based on the SIM. * * The returned string will be a well formed BCP-47 language tag, or {@code null} * if no locale could be derived. */ - String getLocaleFromDefaultSim(); + String getSimLocaleForSubscriber(int subId); /** * Requests the modem activity info asynchronously. @@ -1765,6 +1765,24 @@ interface ITelephony { void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback); /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + */ + void setImsProvisioningStatusForCapability(int subId, int capability, int tech, + boolean isProvisioned); + + /** + * Get the provisioning status for the IMS MmTel capability specified. + */ + boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + + /** Is the capability and tech flagged as provisioned in the cache */ + boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); + + /** Set the provisioning for the capability and tech in the cache */ + void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech, + boolean isProvisioned); + + /** * Return an integer containing the provisioning value for the specified provisioning key. */ int getImsProvisioningInt(int subId, int key); diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index 0edc0026722b..c76d153c6112 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -284,10 +284,6 @@ public final class TelephonyPermissions { */ private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid, int uid, String callingPackage, String message) { - // If the device identifier check is enabled then enforce the new access requirements for - // both 1P and 3P apps. - boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1; // Check if the application is a 3P app; if so then a separate setting is required to relax // the check to begin flagging problems with 3P apps early. boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(), @@ -300,6 +296,11 @@ public final class TelephonyPermissions { context.getContentResolver(), Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, 0) == 1; boolean isNonPrivApp = false; + // Similar to above support relaxing the check for privileged apps while still enforcing it + // for non-privileged and 3P apps. + boolean relaxPrivDeviceIdentifierCheck = Settings.Global.getInt( + context.getContentResolver(), + Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, 0) == 1; ApplicationInfo callingPackageInfo = null; try { callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0); @@ -315,37 +316,28 @@ public final class TelephonyPermissions { Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage, e); } - Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message - + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp); - // The new Q restrictions for device identifier access will be enforced if any of the - // following are true: - // - The PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED setting has been set. - // - The app requesting a device identifier is not a preloaded app (3P), and the - // PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED setting has not been set. - // - The app requesting a device identifier is a preloaded app but is not a privileged app, - // and the PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED setting has not been set. - if (enableDeviceIdentifierCheck + // The new Q restrictions for device identifier access will be enforced for all apps with + // settings to individually disable the new restrictions for privileged, preloaded + // non-privileged, and 3P apps. + if ((!is3PApp && !isNonPrivApp && !relaxPrivDeviceIdentifierCheck) || (is3PApp && !relax3PDeviceIdentifierCheck) || (isNonPrivApp && !relaxNonPrivDeviceIdentifierCheck)) { - boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0; - if (callingPackage != null) { - // if the target SDK is pre-Q or the target Q behavior is disabled then check if - // the calling package would have previously had access to device identifiers. - if (callingPackageInfo != null && ( - callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q - || targetQBehaviorDisabled)) { - if (context.checkPermission( - android.Manifest.permission.READ_PHONE_STATE, - pid, - uid) == PackageManager.PERMISSION_GRANTED) { - return false; - } - if (SubscriptionManager.isValidSubscriptionId(subId) - && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - return false; - } + Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp); + // if the target SDK is pre-Q then check if the calling package would have previously + // had access to device identifiers. + if (callingPackageInfo != null && ( + callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) { + if (context.checkPermission( + android.Manifest.permission.READ_PHONE_STATE, + pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (SubscriptionManager.isValidSubscriptionId(subId) + && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return false; } } throw new SecurityException(message + ": The user " + uid diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk index ee2ec0a80b03..ee02a72b6819 100644 --- a/tests/DexLoggerIntegrationTests/Android.mk +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -29,6 +29,32 @@ include $(BUILD_JAVA_LIBRARY) dexloggertest_jar := $(LOCAL_BUILT_MODULE) +# Also build a native library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeTestLibrary +LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) +LOCAL_SDK_VERSION := 28 +LOCAL_NDK_STL_VARIANT := c++_static + +include $(BUILD_SHARED_LIBRARY) + +# And a standalone native executable that we can exec. + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeExecutable +LOCAL_SRC_FILES := src/cpp/test_executable.cpp + +include $(BUILD_EXECUTABLE) + +dexloggertest_executable := $(LOCAL_BUILT_MODULE) + # Build the test app itself include $(CLEAR_VARS) @@ -37,14 +63,22 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := platform +LOCAL_CERTIFICATE := shared LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ truth-prebuilt \ -# This gets us the javalib.jar built by DexLoggerTestLibrary above. -LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar) +# Include both versions of the .so if we have 2 arch +LOCAL_MULTILIB := both +LOCAL_JNI_SHARED_LIBRARIES := \ + DexLoggerNativeTestLibrary \ + +# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various +# native binaries. +LOCAL_JAVA_RESOURCE_FILES := \ + $(dexloggertest_jar) \ + $(dexloggertest_executable) \ include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java index 75ee0896c23a..e92cc56322eb 100644 --- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java @@ -17,14 +17,17 @@ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.app.UiAutomation; import android.content.Context; +import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.util.EventLog; +import android.util.EventLog.Event; import dalvik.system.DexClassLoader; @@ -65,14 +68,13 @@ public final class DexLoggerIntegrationTests { // Event log tag used for SNET related events private static final int SNET_TAG = 0x534e4554; - // Subtag used to distinguish dynamic code loading events - private static final String DCL_SUBTAG = "dcl"; + // Subtags used to distinguish dynamic code loading events + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; - // All the tags we care about - private static final int[] TAG_LIST = new int[] { SNET_TAG }; - - // This is {@code DynamicCodeLoggingService#JOB_ID} - private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028; + // These are job IDs from DynamicCodeLoggingService + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; private static Context sContext; private static int sMyUid; @@ -89,15 +91,20 @@ public final class DexLoggerIntegrationTests { // Without this the first test passes and others don't - we don't see new events in the // log. The exact reason is unclear. EventLog.writeEvent(SNET_TAG, "Dummy event"); + + // Audit log messages are throttled by the kernel (at the request of logd) to 5 per + // second, so running the tests too quickly in sequence means we lose some and get + // spurious failures. Sigh. + SystemClock.sleep(1000); } @Test - public void testDexLoggerGeneratesEvents() throws Exception { - File privateCopyFile = fileForJar("copied.jar"); + public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception { + File privateCopyFile = privateFile("copied.jar"); // Obtained via "echo -n copied.jar | sha256sum" String expectedNameHash = "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // Feed the jar to a class loader and make sure it contains what we expect. ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); @@ -107,18 +114,18 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); } @Test - public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception { - File privateCopyFile = fileForJar("copied2.jar"); + File privateCopyFile = privateFile("copied2.jar"); String expectedNameHash = "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // This time make sure an unknown class loader is an ancestor of the class loader we use. ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); @@ -129,22 +136,191 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception { + File privateCopyFile = privateFile("copied.so"); + String expectedNameHash = + "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E"; + String expectedContentHash = + copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception { + // A file name with a space will be escaped in the audit log; verify we un-escape it + // correctly. + File privateCopyFile = privateFile("second copy.so"); + String expectedNameHash = + "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA"; + String expectedContentHash = + copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception { + File privateCopyFile = privateFile("test_executable"); + String expectedNameHash = + "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + assertThat(privateCopyFile.setExecutable(true)).isTrue(); + + Process process = Runtime.getRuntime().exec(privateCopyFile.toString()); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static File fileForJar(String name) { - return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name); + @Test + public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception { + File privateCopyFile = privateFile("spoofed"); + + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + privateCopyFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static String copyAndHashJar(File copyTo) throws Exception { + @Test + public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception { + File privateDir = privateFile("x").getParentFile(); + + // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root + File pathTraversalToRoot = privateDir; + File root = new File("/"); + while (!privateDir.equals(root)) { + pathTraversalToRoot = new File(pathTraversalToRoot, ".."); + privateDir = privateDir.getParentFile(); + } + + File spoofedFile = new File(pathTraversalToRoot, "dev/urandom"); + + assertWithMessage("Expected " + spoofedFile + " to be readable") + .that(spoofedFile.canRead()).isTrue(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + spoofedFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception { + File ourPath = sContext.getDatabasePath("android_pay"); + File targetPath = new File(ourPath.toString() + .replace("com.android.frameworks.dexloggertest", "com.google.android.gms")); + + assertWithMessage("Expected " + targetPath + " to not be readable") + .that(targetPath.canRead()).isFalse(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + targetPath + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + private static File privateFile(String name) { + return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name); + } + + private String libraryPath(final String libraryName) { + // This may be deprecated. but it tells us the ABI of this process which is exactly what we + // want. + return "/lib/" + Build.CPU_ABI + "/" + libraryName; + } + + private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception { MessageDigest hasher = MessageDigest.getInstance("SHA-256"); // Copy the jar from our Java resources to a private data directory Class<?> thisClass = DexLoggerIntegrationTests.class; - try (InputStream input = thisClass.getResourceAsStream("/javalib.jar"); - OutputStream output = new FileOutputStream(copyTo)) { + try (InputStream input = thisClass.getResourceAsStream(resourcePath); + OutputStream output = new FileOutputStream(copyTo)) { byte[] buffer = new byte[1024]; while (true) { int numRead = input.read(buffer); @@ -166,24 +342,18 @@ public final class DexLoggerIntegrationTests { return formatter.toString(); } - private static long mostRecentEventTimeNanos() throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - - EventLog.readEvents(TAG_LIST, events); - return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); - } - - private static void runDexLogger() throws Exception { - // This forces {@code DynamicCodeLoggingService} to start now. - runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID); + private static void runDynamicCodeLoggingJob(int jobId) throws Exception { + // This forces the DynamicCodeLoggingService job to start now. + runCommand("cmd jobscheduler run -f android " + jobId); // Wait for the job to have run. long startTime = SystemClock.elapsedRealtime(); while (true) { String response = runCommand( - "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID); + "cmd jobscheduler get-job-state android " + jobId); if (!response.contains("pending") && !response.contains("active")) { break; } + // Don't wait forever - if it's taken > 10s then something is very wrong. if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) { throw new AssertionError("Job has not completed: " + response); } @@ -208,37 +378,68 @@ public final class DexLoggerIntegrationTests { return response.toString("UTF-8"); } - private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash, - String expectedContentHash) throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - EventLog.readEvents(TAG_LIST, events); - int found = 0; - for (EventLog.Event event : events) { + private static long mostRecentEventTimeNanos() throws Exception { + List<Event> events = readSnetEvents(); + return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + } + + private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash, String expectedContentHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1); + assertThat(messages.get(0)).endsWith(expectedContentHash); + } + + private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected no matching log entries").that(messages).isEmpty(); + } + + private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = new ArrayList<>(); + + for (Event event : readSnetEvents()) { if (event.getTimeNanos() <= previousEventNanos) { continue; } - Object[] data = (Object[]) event.getData(); + + Object data = event.getData(); + if (!(data instanceof Object[])) { + continue; + } + Object[] fields = (Object[]) data; // We only care about DCL events that we generated. - String subTag = (String) data[0]; - if (!DCL_SUBTAG.equals(subTag)) { + String subTag = (String) fields[0]; + if (!expectedSubTag.equals(subTag)) { continue; } - int uid = (int) data[1]; + int uid = (int) fields[1]; if (uid != sMyUid) { continue; } - String message = (String) data[2]; + String message = (String) fields[2]; if (!message.startsWith(expectedNameHash)) { continue; } - assertThat(message).endsWith(expectedContentHash); - ++found; + messages.add(message); + //assertThat(message).endsWith(expectedContentHash); } + return messages; + } - assertThat(found).isEqualTo(1); + private static List<Event> readSnetEvents() throws Exception { + List<Event> events = new ArrayList<>(); + EventLog.readEvents(new int[] { SNET_TAG }, events); + return events; } /** diff --git a/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp new file mode 100644 index 000000000000..060888310b51 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" + +extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */) +{ + return JNI_VERSION_1_6; +} diff --git a/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp new file mode 100644 index 000000000000..ad025e696dec --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int main() { + // This program just has to run, it doesn't need to do anything. So we don't. + return 0; +} diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk index 11ea954c62c7..79072faeaa0e 100644 --- a/tests/HwAccelerationTest/Android.mk +++ b/tests/HwAccelerationTest/Android.mk @@ -21,6 +21,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := HwAccelerationTest LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_CERTIFICATE := platform LOCAL_MODULE_TAGS := tests diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index c8f96c9f0670..f330b8353b50 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -310,6 +310,15 @@ <category android:name="com.android.test.hwui.TEST" /> </intent-filter> </activity> + + <activity + android:name="PictureCaptureDemo" + android:label="Debug/Picture Capture"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.hwui.TEST" /> + </intent-filter> + </activity> <activity android:name="SmallCircleActivity" diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java new file mode 100644 index 000000000000..029e302d0382 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Picture; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewDebug; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.ProgressBar; + +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class PictureCaptureDemo extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + + final LinearLayout inner = new LinearLayout(this); + inner.setOrientation(LinearLayout.HORIZONTAL); + ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge); + inner.addView(spinner, + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + + inner.addView(new View(this), new LayoutParams(50, 1)); + + Picture picture = new Picture(); + Canvas canvas = picture.beginRecording(100, 100); + canvas.drawColor(Color.RED); + Paint paint = new Paint(); + paint.setTextSize(32); + paint.setColor(Color.BLACK); + canvas.drawText("Hello", 0, 50, paint); + picture.endRecording(); + + ImageView iv1 = new ImageView(this); + iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888)); + inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + + inner.addView(new View(this), new LayoutParams(50, 1)); + + ImageView iv2 = new ImageView(this); + iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE)); + inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + + layout.addView(inner, + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + // For testing with a functor in the tree + WebView wv = new WebView(this); + wv.setWebViewClient(new WebViewClient()); + wv.setWebChromeClient(new WebChromeClient()); + wv.loadUrl("https://google.com"); + layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400)); + + SurfaceView mySurfaceView = new SurfaceView(this); + layout.addView(mySurfaceView, + new LayoutParams(LayoutParams.MATCH_PARENT, 600)); + + setContentView(layout); + + mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { + private AutoCloseable mStopCapture; + + @Override + public void surfaceCreated(SurfaceHolder holder) { + final Random rand = new Random(); + mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView, + mCaptureThread, (picture) -> { + if (rand.nextInt(20) == 0) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + Canvas canvas = holder.lockCanvas(); + if (canvas == null) { + return false; + } + canvas.drawPicture(picture); + holder.unlockCanvasAndPost(canvas); + picture.close(); + return true; + }); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (mStopCapture != null) { + try { + mStopCapture.close(); + } catch (Exception e) { + } + mStopCapture = null; + } + } + }); + } + + ExecutorService mCaptureThread = Executors.newSingleThreadExecutor(); + ExecutorService mExecutor = Executors.newSingleThreadExecutor(); + + Picture deepCopy(Picture src) { + try { + PipedInputStream inputStream = new PipedInputStream(); + PipedOutputStream outputStream = new PipedOutputStream(inputStream); + Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream)); + src.writeToStream(outputStream); + outputStream.close(); + return future.get(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index 07c4a938cf9f..c16efbda1830 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { R.id.benchmark_text_low_hitrate, R.id.benchmark_edit_text_input, R.id.benchmark_overdraw, + R.id.benchmark_bitmap_upload, }; public static class LocalBenchmarksList extends ListFragment { @@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { case R.id.benchmark_text_low_hitrate: case R.id.benchmark_edit_text_input: case R.id.benchmark_overdraw: + case R.id.benchmark_bitmap_upload: case R.id.benchmark_memory_bandwidth: case R.id.benchmark_memory_latency: case R.id.benchmark_power_management: @@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { intent = new Intent(getApplicationContext(), EditTextInputActivity.class); break; case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class); + break; + case R.id.benchmark_bitmap_upload: intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); break; case R.id.benchmark_memory_bandwidth: diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java index 89c6aedd8b5c..5723c599d91a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -229,6 +229,8 @@ public class BenchmarkRegistry { return context.getString(R.string.cpu_gflops_name); case R.id.benchmark_overdraw: return context.getString(R.string.overdraw_name); + case R.id.benchmark_bitmap_upload: + return context.getString(R.string.bitmap_upload_name); default: return "Some Benchmark"; } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java index 787090208d7e..7692836cfacc 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; import com.android.benchmark.ui.automation.Automator; import com.android.benchmark.ui.automation.Interaction; @@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity { final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); - mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload); + + mAutomator = new Automator(name, runId, iteration, getWindow(), new Automator.AutomateCallback() { @Override public void onPostAutomate() { diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml index 6801fd9f61ec..694e0d9a6917 100644 --- a/tests/JankBench/app/src/main/res/values/ids.xml +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -23,6 +23,7 @@ <item name="benchmark_text_low_hitrate" type="id" /> <item name="benchmark_edit_text_input" type="id" /> <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_bitmap_upload" type="id" /> <item name="benchmark_memory_bandwidth" type="id" /> <item name="benchmark_memory_latency" type="id" /> <item name="benchmark_power_management" type="id" /> diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml index 270adf89e4ed..5c2405899db9 100644 --- a/tests/JankBench/app/src/main/res/values/strings.xml +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ <string name="edit_text_input_description">Tests edit text input</string> <string name="overdraw_name">Overdraw Test</string> <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="bitmap_upload_name">Bitmap Upload Test</string> + <string name="bitmap_upload_description">Tests bitmap upload</string> <string name="memory_bandwidth_name">Memory Bandwidth</string> <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> <string name="memory_latency_name">Memory Latency</string> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml index 07c453c25359..fccc7b9d3776 100644 --- a/tests/JankBench/app/src/main/res/xml/benchmark.xml +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -62,6 +62,12 @@ benchmark:category="ui" benchmark:description="@string/overdraw_description" /> + <com.android.benchmark.Benchmark + benchmark:name="@string/bitmap_upload_name" + benchmark:id="@id/benchmark_bitmap_upload" + benchmark:category="ui" + benchmark:description="@string/bitmap_upload_description" /> + <!-- <com.android.benchmark.Benchmark benchmark:name="@string/memory_bandwidth_name" diff --git a/tests/RollbackTest/Android.mk b/tests/RollbackTest/Android.mk index 34aa258bf465..780bb24e437b 100644 --- a/tests/RollbackTest/Android.mk +++ b/tests/RollbackTest/Android.mk @@ -36,6 +36,17 @@ LOCAL_PACKAGE_NAME := RollbackTestAppAv2 include $(BUILD_PACKAGE) ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE) +# RollbackTestAppACrashingV2.apk +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src) +LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml +LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2 +include $(BUILD_PACKAGE) +ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE) + # RollbackTestAppBv1.apk include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional @@ -68,6 +79,7 @@ LOCAL_COMPATIBILITY_SUITE := general-tests LOCAL_JAVA_RESOURCE_FILES := \ $(ROLLBACK_TEST_APP_AV1) \ $(ROLLBACK_TEST_APP_AV2) \ + $(ROLLBACK_TEST_APP_A_CRASHING_V2) \ $(ROLLBACK_TEST_APP_BV1) \ $(ROLLBACK_TEST_APP_BV2) LOCAL_SDK_VERSION := system_current @@ -77,5 +89,6 @@ include $(BUILD_PACKAGE) # Clean up local variables ROLLBACK_TEST_APP_AV1 := ROLLBACK_TEST_APP_AV2 := +ROLLBACK_TEST_APP_A_CRASHING_V2 := ROLLBACK_TEST_APP_BV1 := ROLLBACK_TEST_APP_BV2 := diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml new file mode 100644 index 000000000000..5708d2385f01 --- /dev/null +++ b/tests/RollbackTest/TestApp/ACrashingV2.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.rollback.testapp.A" + android:versionCode="2" + android:versionName="2.0" > + + + <uses-sdk android:minSdkVersion="19" /> + + <application android:label="Rollback Test App A v2"> + <meta-data android:name="version" android:value="2" /> + <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" + android:exported="true" /> + <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.h b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java index 531379226bb0..02a439b5dd69 100644 --- a/cmds/statsd/src/external/ResourceThermalManagerPuller.h +++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +14,20 @@ * limitations under the License. */ -#pragma once +package com.android.tests.rollback.testapp; -#include <utils/String16.h> -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { +import android.app.Activity; +import android.os.Bundle; /** - * Reads IThermal.hal + * A crashing test app for testing apk rollback support. */ -class ResourceThermalManagerPuller : public StatsPuller { -public: - ResourceThermalManagerPuller(); +public class CrashingMainActivity extends Activity { -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; -}; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); -} // namespace statsd -} // namespace os -} // namespace android
\ No newline at end of file + throw new RuntimeException("Intended force crash"); + } +} diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java index d3c39f0b8248..e10f866c899f 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java @@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit; /** * A broadcast receiver that can be used to get - * ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts. + * ACTION_ROLLBACK_COMMITTED broadcasts. */ class RollbackBroadcastReceiver extends BroadcastReceiver { @@ -43,8 +43,7 @@ class RollbackBroadcastReceiver extends BroadcastReceiver { */ RollbackBroadcastReceiver() { IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); - filter.addDataScheme("package"); + filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED); InstrumentationRegistry.getContext().registerReceiver(this, filter); } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index c2e735e184b0..7ab716fac6df 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -22,20 +22,17 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.support.test.InstrumentationRegistry; import android.util.Log; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Ignore; @@ -43,6 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -58,6 +56,7 @@ public class RollbackTest { private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B"; + private static final String INSTRUMENTED_APP = "com.android.tests.rollback"; /** * Test basic rollbacks. @@ -85,8 +84,8 @@ public class RollbackTest { Manifest.permission.DELETE_PACKAGES, Manifest.permission.MANAGE_ROLLBACKS); - // Register a broadcast receiver for notification when the rollback is - // done executing. + // Register a broadcast receiver for notification when the + // rollback has been committed. RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); RollbackManager rm = RollbackTestUtils.getRollbackManager(); @@ -98,12 +97,11 @@ public class RollbackTest { // uninstalled and when rollback manager deletes the rollback. Fix it // so that's not the case! for (int i = 0; i < 5; ++i) { - for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(info.targetPackage.packageName)) { - Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); - Thread.sleep(1000); - break; - } + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + if (rollback != null) { + Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); + Thread.sleep(1000); } } @@ -112,13 +110,11 @@ public class RollbackTest { // between when the app is uninstalled and when the previously // available rollback, if any, is removed. Thread.sleep(1000); - assertNull(rm.getAvailableRollback(TEST_APP_A)); - assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); + assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); - // There should be no recently executed rollbacks for this package. - for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) { - assertNotEquals(TEST_APP_A, info.targetPackage.packageName); - } + // There should be no recently committed rollbacks for this package. + assertNull(getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A)); // Install v1 of the app (without rollbacks enabled). RollbackTestUtils.install("RollbackTestAppAv1.apk", false); @@ -133,12 +129,9 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - assertEquals(TEST_APP_A, rollback.targetPackage.packageName); - assertEquals(2, rollback.targetPackage.higherVersion.versionCode); - assertEquals(1, rollback.targetPackage.lowerVersion.versionCode); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // We should not have received any rollback requests yet. // TODO: Possibly flaky if, by chance, some other app on device @@ -154,21 +147,12 @@ public class RollbackTest { // received could lead to test flakiness. Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS); assertNotNull(broadcast); - assertEquals(TEST_APP_A, broadcast.getData().getSchemeSpecificPart()); assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS)); // Verify the recent rollback has been recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.packageName)) { - assertNull(rollback); - rollback = r; - } - } - assertNotNull(rollback); - assertEquals(TEST_APP_A, rollback.targetPackage.packageName); - assertEquals(2, rollback.targetPackage.higherVersion.versionCode); - assertEquals(1, rollback.targetPackage.lowerVersion.versionCode); + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); broadcastReceiver.unregister(); context.unregisterReceiver(enableRollbackReceiver); @@ -206,36 +190,25 @@ public class RollbackTest { // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName); - assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName); - assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName); - assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode); - - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName); - assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode); + rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + + rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Rollback of B should not rollback A RollbackTestUtils.rollback(rollbackB); @@ -276,36 +249,23 @@ public class RollbackTest { // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName); - assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName); - assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName); - assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode); - - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName); - assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode); - assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode); + rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollbackA); + + rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); // Rollback of B should rollback A as well RollbackTestUtils.rollback(rollbackB); @@ -317,10 +277,10 @@ public class RollbackTest { } /** - * Test that recently executed rollback data is properly persisted. + * Test that recently committed rollback data is properly persisted. */ @Test - public void testRecentlyExecutedRollbackPersistence() throws Exception { + public void testRecentlyCommittedRollbackPersistence() throws Exception { try { RollbackTestUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, @@ -339,41 +299,27 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); // Roll back the app. RollbackTestUtils.rollback(rollback); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); // Verify the recent rollback has been recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.packageName)) { - assertNull(rollback); - rollback = r; - } - } + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); assertNotNull(rollback); - assertEquals(TEST_APP_A, rollback.targetPackage.packageName); - assertEquals(2, rollback.targetPackage.higherVersion.versionCode); - assertEquals(1, rollback.targetPackage.lowerVersion.versionCode); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // Reload the persisted data. rm.reloadPersistedData(); // Verify the recent rollback is still recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.packageName)) { - assertNull(rollback); - rollback = r; - } - } + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); assertNotNull(rollback); - assertEquals(TEST_APP_A, rollback.targetPackage.packageName); - assertEquals(2, rollback.targetPackage.higherVersion.versionCode); - assertEquals(1, rollback.targetPackage.lowerVersion.versionCode); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } @@ -402,19 +348,16 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - assertEquals(TEST_APP_A, rollback.targetPackage.packageName); - assertEquals(2, rollback.targetPackage.higherVersion.versionCode); - assertEquals(1, rollback.targetPackage.lowerVersion.versionCode); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // Expire the rollback. rm.expireRollbackForPackage(TEST_APP_A); // The rollback should no longer be available. - assertNull(rm.getAvailableRollback(TEST_APP_A)); - assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); + assertNull(getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A)); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } @@ -470,7 +413,7 @@ public class RollbackTest { * Test that app user data is rolled back. * TODO: Stop ignoring this test once user data rollback is supported. */ - @Ignore @Test + @Test public void testUserDataRollback() throws Exception { try { RollbackTestUtils.adoptShellPermissionIdentity( @@ -479,13 +422,14 @@ public class RollbackTest { Manifest.permission.MANAGE_ROLLBACKS); RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV1.apk", false); + RollbackTestUtils.install("RollbackTestAppAv1.apk", false); processUserData(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV2.apk", true); + RollbackTestUtils.install("RollbackTestAppAv2.apk", true); processUserData(TEST_APP_A); RollbackManager rm = RollbackTestUtils.getRollbackManager(); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); RollbackTestUtils.rollback(rollback); processUserData(TEST_APP_A); } finally { @@ -495,13 +439,12 @@ public class RollbackTest { /** * Test restrictions on rollback broadcast sender. - * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast. + * A random app should not be able to send a ROLLBACK_COMMITTED broadcast. */ @Test public void testRollbackBroadcastRestrictions() throws Exception { RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, - Uri.fromParts("package", "com.android.tests.rollback.bogus", null)); + Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); try { InstrumentationRegistry.getContext().sendBroadcast(broadcast); fail("Succeeded in sending restricted broadcast from app context."); @@ -543,18 +486,18 @@ public class RollbackTest { assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); // Both test apps should now be available for rollback, and the - // targetPackage returned for rollback should be correct. + // RollbackInfo returned for the rollbacks should be correct. // TODO: See if there is a way to remove this race condition // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Executing rollback should roll back the correct package. RollbackTestUtils.rollback(rollbackA); @@ -585,21 +528,14 @@ public class RollbackTest { RollbackManager rm = RollbackTestUtils.getRollbackManager(); try { - rm.getAvailableRollback(TEST_APP_A); + rm.getAvailableRollbacks(); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. } try { - rm.getPackagesWithAvailableRollbacks(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.getRecentlyExecutedRollbacks(); + rm.getRecentlyCommittedRollbacks(); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. @@ -608,7 +544,7 @@ public class RollbackTest { try { // TODO: What if the implementation checks arguments for non-null // first? Then this test isn't valid. - rm.executeRollback(null, null); + rm.commitRollback(null, null); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. @@ -656,12 +592,9 @@ public class RollbackTest { assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); // TEST_APP_A should now be available for rollback. - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - - // TODO: Test the dependent apps for rollback are correct once we - // support that in the RollbackInfo API. + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollback); // Rollback the app. It should cause both test apps to be rolled // back. @@ -669,16 +602,131 @@ public class RollbackTest { assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); - // We should not see a recent rollback listed for TEST_APP_B - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - assertNotEquals(TEST_APP_B, r.targetPackage.packageName); + // We should see recent rollbacks listed for both A and B. + Thread.sleep(1000); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); + + assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId()); + } finally { + RollbackTestUtils.dropShellPermissionIdentity(); + } + } + + // Helper function to test the value of a PackageRollbackInfo + private void assertPackageRollbackInfoEquals(String packageName, + long versionRolledBackFrom, long versionRolledBackTo, + PackageRollbackInfo info) { + assertEquals(packageName, info.getPackageName()); + assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName()); + assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode()); + assertEquals(packageName, info.getVersionRolledBackTo().getPackageName()); + assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode()); + } + + // TODO(zezeozue): Stop ignoring after fixing race between rolling back and testing version + /** + * Test bad update automatic rollback. + */ + @Ignore("Flaky") + @Test + public void testBadUpdateRollback() throws Exception { + try { + RollbackTestUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.MANAGE_ROLLBACKS); + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + + // Prep installation of the test apps. + RollbackTestUtils.uninstall(TEST_APP_A); + RollbackTestUtils.install("RollbackTestAppAv1.apk", false); + RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true); + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + + RollbackTestUtils.uninstall(TEST_APP_B); + RollbackTestUtils.install("RollbackTestAppBv1.apk", false); + RollbackTestUtils.install("RollbackTestAppBv2.apk", true); + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + + // Both test apps should now be available for rollback, and the + // targetPackage returned for rollback should be correct. + // TODO: See if there is a way to remove this race condition + // between when the app is installed and when the rollback + // is made available. + Thread.sleep(1000); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); + + // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes + for (int i = 0; i < 5; i++) { + RollbackTestUtils.launchPackage(TEST_APP_A); + Thread.sleep(1000); } + Thread.sleep(1000); - // TODO: Test the listed dependent apps for the recently executed - // rollback are correct once we support that in the RollbackInfo - // API. + // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver + assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + // Instrumented app is still the package installer + Context context = InstrumentationRegistry.getContext(); + String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A); + assertEquals(INSTRUMENTED_APP, installer); + // TEST_APP_B is untouched + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } } + + // Helper function to test the value of a RollbackInfo with single package + private void assertRollbackInfoEquals(String packageName, + long versionRolledBackFrom, long versionRolledBackTo, + RollbackInfo info) { + assertNotNull(info); + assertEquals(1, info.getPackages().size()); + assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo, + info.getPackages().get(0)); + } + + // Helper function to test that the given rollback info is a rollback for + // the atomic set {A2, B2} -> {A1, B1}. + private void assertRollbackInfoForAandB(RollbackInfo rollback) { + assertNotNull(rollback); + assertEquals(2, rollback.getPackages().size()); + if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) { + assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0)); + assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1)); + } else { + assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0)); + assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1)); + } + } + + // Helper function to return the RollbackInfo with a given package in the + // list of rollbacks. Throws an assertion failure if there is more than + // one such rollback info. Returns null if there are no such rollback + // infos. + private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks, + String packageName) { + RollbackInfo found = null; + for (RollbackInfo rollback : rollbacks) { + for (PackageRollbackInfo info : rollback.getPackages()) { + if (packageName.equals(info.getPackageName())) { + assertNull(found); + found = rollback; + break; + } + } + } + return found; + } } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index fbc3d8f1cd34..f481897c060c 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -90,12 +90,12 @@ class RollbackTestUtils { } /** - * Execute the given rollback. + * Commit the given rollback. * @throws AssertionError if the rollback fails. */ static void rollback(RollbackInfo rollback) throws InterruptedException { RollbackManager rm = getRollbackManager(); - rm.executeRollback(rollback, LocalIntentSender.getIntentSender()); + rm.commitRollback(rollback, LocalIntentSender.getIntentSender()); assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); } @@ -135,6 +135,17 @@ class RollbackTestUtils { assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); } + /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */ + static void launchPackage(String packageName) + throws InterruptedException, IOException { + Context context = InstrumentationRegistry.getContext(); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setPackage(packageName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + context.startActivity(intent); + } + /** * Installs the apks with the given resource names as an atomic set. * diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml index 4b1c1bd69920..fefd99394a87 100644 --- a/tests/UsageStatsTest/AndroidManifest.xml +++ b/tests/UsageStatsTest/AndroidManifest.xml @@ -11,6 +11,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" /> + <uses-permission android:name="android.permission.SUSPEND_APPS" /> <application android:label="Usage Access Test"> <activity android:name=".UsageStatsActivity" diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml index 612267c85b1b..272e0f4e1f54 100644 --- a/tests/UsageStatsTest/res/menu/main.xml +++ b/tests/UsageStatsTest/res/menu/main.xml @@ -6,4 +6,6 @@ android:title="Call isAppInactive()"/> <item android:id="@+id/set_app_limit" android:title="Set App Limit" /> + <item android:id="@+id/set_app_usage_limit" + android:title="Set App Usage Limit" /> </menu> diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java index 3c628f6e0013..0105893adf9e 100644 --- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java +++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java @@ -21,6 +21,8 @@ import android.app.ListActivity; import android.app.PendingIntent; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -49,6 +51,8 @@ public class UsageStatsActivity extends ListActivity { private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT"; private UsageStatsManager mUsageStatsManager; + private ClipboardManager mClipboard; + private ClipData mClip; private Adapter mAdapter; private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() { @Override @@ -61,6 +65,7 @@ public class UsageStatsActivity extends ListActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); + mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); mAdapter = new Adapter(); setListAdapter(mAdapter); Bundle extras = getIntent().getExtras(); @@ -98,6 +103,8 @@ public class UsageStatsActivity extends ListActivity { case R.id.set_app_limit: callSetAppLimit(); return true; + case R.id.set_app_usage_limit: + callSetAppUsageLimit(); default: return super.onOptionsItemSelected(item); } @@ -170,6 +177,40 @@ public class UsageStatsActivity extends ListActivity { builder.show(); } + private void callSetAppUsageLimit() { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Enter package name"); + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_TEXT); + input.setHint("com.android.tests.usagestats"); + builder.setView(input); + + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final String packageName = input.getText().toString().trim(); + if (!TextUtils.isEmpty(packageName)) { + String[] packages = packageName.split(","); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class); + intent.setPackage(getPackageName()); + intent.putExtra(EXTRA_KEY_TIMEOUT, true); + mUsageStatsManager.registerAppUsageLimitObserver(1, packages, + 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this, + 1, intent, 0)); + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + } + private void showInactive(String packageName) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage( @@ -232,6 +273,21 @@ public class UsageStatsActivity extends ListActivity { holder.packageName.setText(mStats.get(position).getPackageName()); holder.usageTime.setText(DateUtils.formatDuration( mStats.get(position).getTotalTimeInForeground())); + + //copy package name to the clipboard for convenience + holder.packageName.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + String text = holder.packageName.getText().toString(); + mClip = ClipData.newPlainText("package_name", text); + mClipboard.setPrimaryClip(mClip); + + Toast.makeText(getApplicationContext(), "package name copied to clipboard", + Toast.LENGTH_SHORT).show(); + return true; + } + }); + return convertView; } } diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 685067377166..7e1b4008c473 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -32,74 +32,6 @@ LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform -# These are not normally accessible from apps so they must be explicitly included. -LOCAL_JNI_SHARED_LIBRARIES := \ - android.hidl.token@1.0 \ - libartbase \ - libbacktrace \ - libbase \ - libbinder \ - libbinderthreadstate \ - libc++ \ - libcrypto \ - libcutils \ - libdexfile \ - libframeworksnettestsjni \ - libhidl-gen-utils \ - libhidlbase \ - libhidltransport \ - libhwbinder \ - liblog \ - liblzma \ - libnativehelper \ - libpackagelistparser \ - libpcre2 \ - libprocessgroup \ - libselinux \ - libui \ - libutils \ - libvintf \ - libvndksupport \ - libtinyxml2 \ - libunwindstack \ - libutilscallstack \ - libziparchive \ - libz \ - netd_aidl_interface-cpp - LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk include $(BUILD_PACKAGE) - -######################################################################### -# Build JNI Shared Library -######################################################################### - -LOCAL_PATH:= $(LOCAL_PATH)/jni - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_CFLAGS := -Wall -Wextra -Werror - -LOCAL_C_INCLUDES := \ - libpcap \ - hardware/google/apf - -LOCAL_SRC_FILES := $(call all-cpp-files-under) - -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - liblog \ - libcutils \ - libnativehelper \ - netd_aidl_interface-cpp - -LOCAL_STATIC_LIBRARIES := \ - libpcap \ - libapf - -LOCAL_MODULE := libframeworksnettestsjni - -include $(BUILD_SHARED_LIBRARY) diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java new file mode 100644 index 000000000000..032e52666970 --- /dev/null +++ b/tests/net/java/android/net/DnsPacketTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketTest { + private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, + int qCount, int aCount, int nsCount, int arCount) { + assertEquals(header.id, id); + assertEquals(header.flags, flag); + assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount); + assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount); + assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount); + assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount); + } + + private void assertSectionParses(DnsPacket.DnsSection section, String dname, + int dtype, int dclass, int ttl, byte[] rr) { + assertEquals(section.dName, dname); + assertEquals(section.nsType, dtype); + assertEquals(section.nsClass, dclass); + assertEquals(section.ttl, ttl); + assertTrue(Arrays.equals(section.getRR(), rr)); + } + + class TestDnsPacket extends DnsPacket { + TestDnsPacket(byte[] data) throws ParseException { + super(data); + } + + public DnsHeader getHeader() { + return mHeader; + } + public List<DnsSection> getSectionList(int secType) { + return mSections[secType]; + } + } + + @Test + public void testNullDisallowed() { + try { + new TestDnsPacket(null); + fail("Exception not thrown for null byte array"); + } catch (DnsPacket.ParseException e) { + } + } + + @Test + public void testV4Answer() throws Exception { + final byte[] v4blob = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v4blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b, + new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); + } + + @Test + public void testV6Answer() throws Exception { + final byte[] v6blob = new byte[] { + /* Header */ + 0x77, 0x22, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x37, /* TTL */ + 0x00, 0x10, /* Data length */ + 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v6blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37, + new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1c264184db4c..923c7dd5fb94 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -107,6 +107,8 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; @@ -121,7 +123,9 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkUtils; +import android.net.ProxyInfo; import android.net.RouteInfo; +import android.net.SocketKeepalive; import android.net.UidRange; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; @@ -158,6 +162,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; @@ -186,6 +191,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -402,8 +409,8 @@ public class ConnectivityServiceTest { private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; - private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; - private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; // Contains the redirectUrl from networkStatus(). Before reading, wait for // mNetworkStatusReceived. @@ -1002,6 +1009,11 @@ public class ConnectivityServiceTest { } @Override + protected ProxyTracker makeProxyTracker() { + return mock(ProxyTracker.class); + } + + @Override protected int reserveNetId() { while (true) { final int netId = super.reserveNetId(); @@ -1023,6 +1035,11 @@ public class ConnectivityServiceTest { } } + @Override + protected boolean queryUserAccess(int uid, int netId) { + return true; + } + public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; } @@ -1508,6 +1525,12 @@ public class ConnectivityServiceTest { verifyActiveNetwork(TRANSPORT_WIFI); } + @Test + public void testRequiresValidation() { + assertTrue(NetworkMonitorUtils.isValidationRequired( + mCm.getDefaultRequest().networkCapabilities)); + } + enum CallbackState { NONE, AVAILABLE, @@ -3542,6 +3565,80 @@ public class ConnectivityServiceTest { } } + private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback { + + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = SocketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue + && this.callbackType == ((CallbackValue) o).callbackType + && this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, + error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } + } + private Network connectKeepaliveNetwork(LinkProperties lp) { // Ensure the network is disconnected before we do anything. if (mWiFiNetworkAgent != null) { @@ -3689,6 +3786,145 @@ public class ConnectivityServiceTest { } @Test + public void testNattSocketKeepalives() throws Exception { + // TODO: 1. Move this outside of ConnectivityServiceTest. + // 2. Add helper function to test against newSingleThreadExecutor as well as inline + // executor. + // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService. + final int srcPort = 12345; + final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + final int validKaInterval = 15; + final int invalidKaInterval = 9; + + final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + + final Executor executor = Executors.newSingleThreadExecutor(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(); + SocketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + // Invalid network. + ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // Invalid interval. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(invalidKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL); + + // Invalid destination. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Invalid source; + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // NAT-T is only supported for IPv4. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Sanity check before testing started keepalive. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + waitForIdle(); + final Network myNetAlias = myNet; + assertNull(mCm.getNetworkCapabilities(myNetAlias)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(); + SocketKeepalive ka2 = + mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2); + ka2.start(validKaInterval); + callback2.expectStarted(); + + ka.stop(); + callback.expectStopped(); + + ka2.stop(); + callback2.expectStopped(); + } + + @Test public void testGetCaptivePortalServerUrl() throws Exception { String url = mCm.getCaptivePortalServerUrl(); assertEquals("http://connectivitycheck.gstatic.com/generate_204", url); @@ -4404,8 +4640,7 @@ public class ConnectivityServiceTest { mMockVpn.setUids(ranges); // VPN networks do not satisfy the default request and are automatically validated // by NetworkMonitor - assertFalse(NetworkMonitorUtils.isValidationRequired( - mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities)); + assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities)); vpnNetworkAgent.setNetworkValid(); vpnNetworkAgent.connect(false); @@ -4909,4 +5144,84 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(lp); verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES); } + + @Test + public void testGetGlobalProxyForNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); + assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); + } + + @Test + public void testGetProxyForActiveNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + final LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testGetProxyForVPN() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + + // Set up a WiFi network with no proxy + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Set up a VPN network with a proxy + final int uid = Process.myUid(); + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + mMockVpn.setUids(ranges); + LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + // Connect to VPN with proxy + mMockVpn.setNetworkAgent(vpnNetworkAgent); + vpnNetworkAgent.connect(true); + mMockVpn.connect(); + waitForIdle(); + + // Test that the VPN network returns a proxy, and the WiFi does not. + assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + + // Test that the VPN network returns no proxy when it is set to null. + testLinkProperties.setHttpProxy(null); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(null)); + + // Set WiFi proxy and check that the vpn proxy is still null. + testLinkProperties.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Disconnect from VPN and check that the active network, which is now the WiFi, has the + // correct proxy setting. + vpnNetworkAgent.disconnect(); + waitForIdle(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } } diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index 125fe7258e94..273b8fc3773b 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; @@ -34,25 +35,23 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.telephony.TelephonyManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.runner.RunWith; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest @@ -194,4 +193,54 @@ public class NetworkNotificationManagerTest { mManager.clearNotification(id); verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any()); } + + @Test + public void testSameLevelNotifications() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); + } + + @Test + public void testClearNotificationByType() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + // clearNotification(int id, NotificationType notifyType) will check if given type is equal + // to previous type or not. If they are equal then clear the notification; if they are not + // equal then return. + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification + // should be cleared. + mManager.clearNotification(id, LOGGED_IN); + verify(mNotificationManager, times(1)) + .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any()); + + mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(2)) + .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + + // LOST_INTERNET notification popup after LOGGED_IN notification. + mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); + + // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification + // shouldn't be cleared. + mManager.clearNotification(id, LOGGED_IN); + // LOST_INTERNET shouldn't be cleared. + verify(mNotificationManager, never()) + .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any()); + } } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index c748d0f5f743..e57433a52cca 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.doReturn; import android.content.Context; import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.IOnBlobRetrievedListener; +import android.net.ipmemorystore.IOnL2KeyResponseListener; import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; import android.net.ipmemorystore.IOnSameNetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; @@ -67,7 +68,14 @@ public class IpMemoryStoreServiceTest { private static final String TEST_CLIENT_ID = "testClientId"; private static final String TEST_DATA_NAME = "testData"; - private static final String[] FAKE_KEYS = { "fakeKey1", "fakeKey2", "fakeKey3", "fakeKey4" }; + private static final int FAKE_KEY_COUNT = 20; + private static final String[] FAKE_KEYS; + static { + FAKE_KEYS = new String[FAKE_KEY_COUNT]; + for (int i = 0; i < FAKE_KEYS.length; ++i) { + FAKE_KEYS[i] = "fakeKey" + i; + } + } @Mock private Context mMockContext; @@ -170,14 +178,35 @@ public class IpMemoryStoreServiceTest { }; } + /** Helper method to make an IOnL2KeyResponseListener */ + private interface OnL2KeyResponseListener { + void onL2KeyResponse(Status status, String key); + } + private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) { + return new IOnL2KeyResponseListener() { + @Override + public void onL2KeyResponse(final StatusParcelable status, final String key) + throws RemoteException { + functor.onL2KeyResponse(new Status(status), key); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + // Helper method to factorize some boilerplate private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) { final CountDownLatch latch = new CountDownLatch(1); functor.accept(latch); try { - latch.await(5000, TimeUnit.MILLISECONDS); + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + fail(timeoutMessage); + } } catch (InterruptedException e) { - fail(timeoutMessage); + fail("Thread was interrupted"); } } @@ -195,12 +224,9 @@ public class IpMemoryStoreServiceTest { } @Test - public void testNetworkAttributes() { + public void testNetworkAttributes() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); - try { - na.setAssignedV4Address( - (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); - } catch (UnknownHostException e) { /* Can't happen */ } + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); na.setGroupHint("hint1"); na.setMtu(219); final String l2Key = FAKE_KEYS[0]; @@ -218,10 +244,8 @@ public class IpMemoryStoreServiceTest { }))); final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder(); - try { - na.setDnsAddresses(Arrays.asList( - new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); - } catch (UnknownHostException e) { /* Still can't happen */ } + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); final NetworkAttributes attributes2 = na2.build(); storeAttributes("Did not complete storing attributes 2", l2Key, attributes2); @@ -292,6 +316,7 @@ public class IpMemoryStoreServiceTest { assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(key); assertNull(attr); + latch.countDown(); }))); } @@ -333,8 +358,100 @@ public class IpMemoryStoreServiceTest { } @Test - public void testFindL2Key() { - // TODO : implement this + public void testFindL2Key() throws UnknownHostException { + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + na.setGroupHint("hint0"); + storeAttributes(FAKE_KEYS[0], na.build()); + + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")})); + na.setMtu(219); + storeAttributes(FAKE_KEYS[1], na.build()); + na.setMtu(null); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); + na.setGroupHint("hint1"); + storeAttributes(FAKE_KEYS[2], na.build()); + na.setMtu(219); + storeAttributes(FAKE_KEYS[3], na.build()); + na.setMtu(240); + storeAttributes(FAKE_KEYS[4], na.build()); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8")); + storeAttributes(FAKE_KEYS[5], na.build()); + + // Matches key 5 exactly + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + latch.countDown(); + }))); + + // MTU matches key 4 but v4 address matches key 5. The latter is stronger. + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + latch.countDown(); + }))); + + // Closest to key 3 (indeed, identical) + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setMtu(219); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + latch.countDown(); + }))); + + // Group hint alone must not be strong enough to override the rest + na.setGroupHint("hint0"); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + latch.countDown(); + }))); + + // Still closest to key 3, though confidence is lower + na.setGroupHint("hint1"); + na.setDnsAddresses(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + latch.countDown(); + }))); + + // But changing the MTU makes this closer to key 4 + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[4], key); + latch.countDown(); + }))); + + // MTU alone not strong enough to make this group-close + na.setGroupHint(null); + na.setDnsAddresses(null); + na.setAssignedV4Address(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertNull(key); + latch.countDown(); + }))); } private void assertNetworksSameness(final String key1, final String key2, final int sameness) { @@ -343,13 +460,14 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(sameness, answer.getNetworkSameness()); + latch.countDown(); }))); } @Test public void testIsSameNetwork() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); - na.setAssignedV4Address((Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); na.setGroupHint("hint1"); na.setMtu(219); na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6"))); @@ -381,6 +499,7 @@ public class IpMemoryStoreServiceTest { + status.resultCode, status.isSuccess()); assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(answer); + latch.countDown(); }))); } } diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java new file mode 100644 index 000000000000..d0350aff5ef5 --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +/** + * JUnit filter to select tests. + * + * <p>This filter selects tests specified by package name, class name, and method name. With this + * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the + * restriction that prevents using the package and the class options can be mitigated. + * + * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2. \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * Note that the ending {@code .} in package name is mandatory. + * + * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA,package2.ClassB \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * <p><b>Select out test methods from Java classes:</b> + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * Those options can be used simultaneously. For example + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * will select out all tests in package1, all tests in classA, and ClassB#methodZ test. + * + * <p>Note that when this option is specified with either {@code -e package} or {@code -e class} + * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage}, + * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected + * with this SelectTest option. + * + * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely + * logs to logcat while parsing {@code -e selectTest} option. + */ +public class SelectTest extends Filter { + + private static final String TAG = SelectTest.class.getSimpleName(); + + @VisibleForTesting + static final String OPTION_SELECT_TEST = "selectTest"; + @VisibleForTesting + static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose"; + + private static final String ARGUMENT_ITEM_SEPARATOR = ","; + private static final String PACKAGE_NAME_SEPARATOR = "."; + private static final String METHOD_SEPARATOR = "#"; + + @Nullable + private final PackageSet mPackageSet; + + /** + * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}. + * + * @param testArgs instrumentation test arguments. + */ + public SelectTest(@NonNull Bundle testArgs) { + mPackageSet = parseSelectTest(testArgs); + } + + @Override + public boolean shouldRun(Description description) { + if (mPackageSet == null) { + // Accept all tests because this filter is disabled. + return true; + } + String testClassName = description.getClassName(); + String testMethodName = description.getMethodName(); + return mPackageSet.accept(testClassName, testMethodName); + } + + @Override + public String describe() { + return OPTION_SELECT_TEST + "=" + mPackageSet; + } + + /** + * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}. + * + * <p>This method is intended to be used at constructor of extended {@link Filter} class. + * + * @param testArgs instrumentation test arguments. + * @param selectTests array of class name to be selected to run. + * @return modified instrumentation test arguments. + */ + @NonNull + protected static Bundle addSelectTest( + @NonNull Bundle testArgs, @NonNull String... selectTests) { + if (selectTests.length == 0) { + return testArgs; + } + testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests))); + return testArgs; + } + + /** + * Parse {@code -e selectTest} argument. + * @param testArgs instrumentation test arguments. + * @return {@link PackageSet} that will filter tests. Returns {@code null} when no + * {@code -e selectTest} option is specified, thus this filter gets disabled. + */ + @Nullable + private static PackageSet parseSelectTest(Bundle testArgs) { + final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST); + if (selectTestArgs == null) { + Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified"); + return null; + } + + final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE)); + final PackageSet packageSet = new PackageSet(verbose); + for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) { + packageSet.add(selectTestArg); + } + return packageSet; + } + + private static String getPackageName(String selectTestArg) { + int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR); + return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos); + } + + @Nullable + private static String getClassName(String selectTestArg) { + if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) { + return null; + } + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos); + } + + @Nullable + private static String getMethodName(String selectTestArg) { + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1); + } + + /** Package level filter */ + private static class PackageSet { + private final boolean mVerbose; + /** + * Java package name to {@link ClassSet} map. To represent package filtering, a map value + * can be {@code null}. + */ + private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>(); + + PackageSet(boolean verbose) { + mVerbose = verbose; + } + + void add(final String selectTestArg) { + final String packageName = getPackageName(selectTestArg); + final String className = getClassName(selectTestArg); + + if (className == null) { + ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering. + if (mVerbose) { + logging("Select package " + selectTestArg, classSet != null, + "; supersede " + classSet); + } + return; + } + + ClassSet classSet = mClassSetMap.get(packageName); + if (classSet == null) { + if (mClassSetMap.containsKey(packageName)) { + if (mVerbose) { + logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true, + " ignore " + selectTestArg); + } + return; + } + classSet = new ClassSet(mVerbose); + mClassSetMap.put(packageName, classSet); + } + classSet.add(selectTestArg); + } + + boolean accept(String className, @Nullable String methodName) { + String packageName = getPackageName(className); + if (!mClassSetMap.containsKey(packageName)) { + return false; + } + ClassSet classSet = mClassSetMap.get(packageName); + return classSet == null || classSet.accept(className, methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String packageName : mClassSetMap.keySet()) { + ClassSet classSet = mClassSetMap.get(packageName); + joiner.add(classSet == null + ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString()); + } + return joiner.toString(); + } + } + + /** Class level filter */ + private static class ClassSet { + private final boolean mVerbose; + /** + * Java class name to set of method names map. To represent class filtering, a map value + * can be {@code null}. + */ + private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>(); + + ClassSet(boolean verbose) { + mVerbose = verbose; + } + + void add(String selectTestArg) { + final String className = getClassName(selectTestArg); + final String methodName = getMethodName(selectTestArg); + + if (methodName == null) { + Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering. + if (mVerbose) { + logging("Select class " + selectTestArg, methodSet != null, + "; supersede " + toString(className, methodSet)); + } + return; + } + + Set<String> methodSet = mMethodSetMap.get(className); + if (methodSet == null) { + if (mMethodSetMap.containsKey(className)) { + if (mVerbose) { + logging("Select class " + className, true, "; ignore " + selectTestArg); + } + return; + } + methodSet = new LinkedHashSet<>(); + mMethodSetMap.put(className, methodSet); + } + + methodSet.add(methodName); + if (mVerbose) { + logging("Select method " + selectTestArg, false, null); + } + } + + boolean accept(String className, @Nullable String methodName) { + if (!mMethodSetMap.containsKey(className)) { + return false; + } + Set<String> methodSet = mMethodSetMap.get(className); + return methodName == null || methodSet == null || methodSet.contains(methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String className : mMethodSetMap.keySet()) { + joiner.add(toString(className, mMethodSetMap.get(className))); + } + return joiner.toString(); + } + + private static String toString(String className, @Nullable Set<String> methodSet) { + if (methodSet == null) { + return className; + } + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String methodName : methodSet) { + joiner.add(className + METHOD_SEPARATOR + methodName); + } + return joiner.toString(); + } + } + + private static void logging(String infoLog, boolean isWarning, String warningLog) { + if (isWarning) { + Log.w(TAG, infoLog + warningLog); + } else { + Log.i(TAG, infoLog); + } + } + + private static String join(Collection<String> list) { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String text : list) { + joiner.add(text); + } + return joiner.toString(); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java new file mode 100644 index 000000000000..163b00abafcd --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST; +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Bundle; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + +public class SelectTestTests { + + private static final String PACKAGE_A = "packageA."; + private static final String PACKAGE_B = "packageB."; + private static final String PACKAGE_C = "packageC."; + private static final String CLASS_A1 = PACKAGE_A + "Class1"; + private static final String CLASS_A2 = PACKAGE_A + "Class2"; + private static final String CLASS_B3 = PACKAGE_B + "Class3"; + private static final String CLASS_B4 = PACKAGE_B + "Class4"; + private static final String CLASS_C5 = PACKAGE_C + "Class5"; + private static final String CLASS_C6 = PACKAGE_C + "Class6"; + private static final String METHOD_A1K = CLASS_A1 + "#methodK"; + private static final String METHOD_A1L = CLASS_A1 + "#methodL"; + private static final String METHOD_A2M = CLASS_A2 + "#methodM"; + private static final String METHOD_A2N = CLASS_A2 + "#methodN"; + private static final String METHOD_B3P = CLASS_B3 + "#methodP"; + private static final String METHOD_B3Q = CLASS_B3 + "#methodQ"; + private static final String METHOD_B4R = CLASS_B4 + "#methodR"; + private static final String METHOD_B4S = CLASS_B4 + "#methodS"; + private static final String METHOD_C5W = CLASS_C5 + "#methodW"; + private static final String METHOD_C5X = CLASS_C5 + "#methodX"; + private static final String METHOD_C6Y = CLASS_C6 + "#methodY"; + private static final String METHOD_C6Z = CLASS_C6 + "#methodZ"; + + private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K); + private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L); + private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M); + private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N); + private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P); + private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q); + private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R); + private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S); + private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W); + private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X); + private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y); + private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z); + private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L); + private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N); + private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q); + private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S); + private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X); + private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z); + private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2); + private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4); + private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6); + private static final Set<Description> TEST_ALL = + merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C); + + private SelectTestBuilder mBuilder; + + @Before + public void setUp() { + mBuilder = new SelectTestBuilder(); + } + + private static class SelectTestBuilder { + private final Bundle mTestArgs = new Bundle(); + + Filter build() { + mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString()); + return new SelectTest(mTestArgs); + } + + SelectTestBuilder withSelectTest(String... selectTestArgs) { + putTestOption(OPTION_SELECT_TEST, selectTestArgs); + return this; + } + + private void putTestOption(String option, String... args) { + if (args.length > 0) { + StringJoiner joiner = new StringJoiner(","); + for (String arg : args) { + joiner.add(arg); + } + mTestArgs.putString(option, joiner.toString()); + } + } + } + + private static Set<Description> methodTest(String testName) { + int methodSep = testName.indexOf("#"); + String className = testName.substring(0, methodSep); + String methodName = testName.substring(methodSep + 1); + final Set<Description> tests = new ArraySet<>(); + tests.add(Description.createSuiteDescription(className)); + tests.add(Description.createTestDescription(className, methodName)); + return Collections.unmodifiableSet(tests); + } + + @SafeVarargs + private static Set<Description> merge(Set<Description>... testSpecs) { + final Set<Description> merged = new LinkedHashSet<>(); + for (Set<Description> testSet : testSpecs) { + merged.addAll(testSet); + } + return Collections.unmodifiableSet(merged); + } + + @SafeVarargs + private static void acceptTests(Filter filter, Set<Description>... testSpecs) { + final Set<Description> accepts = merge(testSpecs); + for (Description test : TEST_ALL) { + if (accepts.contains(test)) { + assertTrue("accept " + test, filter.shouldRun(test)); + } else { + assertFalse("reject " + test, filter.shouldRun(test)); + } + } + } + + @Test + public void testFilterDisabled() { + final Filter filter = mBuilder.build(); + acceptTests(filter, TEST_ALL); + } + + @Test + public void testSelectPackage() { + final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } + + @Test + public void testSelectClass() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3); + } + + @Test + public void testSelectMethod() { + final Filter filter = mBuilder + .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndPackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5); + } + + @Test + public void testSelectMethodAndPackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W); + } + + @Test + public void testSelectMethodAndClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndSameClass() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R); + } + + @Test + public void testSelectMethodAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndClassAndPackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } +} diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 7783e108f674..8f752871355f 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -182,7 +182,8 @@ cc_test_host { defaults: ["aapt2_defaults"], data: [ "integration-tests/CompileTest/**/*", - "integration-tests/CommandTests/**/*" + "integration-tests/CommandTests/**/*", + "integration-tests/ConvertTest/**/*" ], } diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 85f90806752f..7a74ba925ba0 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -284,6 +284,8 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer // The table might be modified by below code. auto converted_table = apk->GetResourceTable(); + std::unordered_set<std::string> files_written; + // Resources for (const auto& package : converted_table->packages) { for (const auto& type : package->types) { @@ -297,10 +299,14 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer return 1; } - if (!serializer->SerializeFile(file, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) - << "failed to serialize file " << *file->path); - return 1; + // Only serialize if we haven't seen this file before + if (files_written.insert(*file->path).second) { + if (!serializer->SerializeFile(file, output_writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize file " + << *file->path); + return 1; + } } } // file } // config_value diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp index 2e4315086105..3c0fe370c516 100644 --- a/tools/aapt2/cmd/Convert_test.cpp +++ b/tools/aapt2/cmd/Convert_test.cpp @@ -18,6 +18,7 @@ #include "LoadedApk.h" #include "test/Test.h" +#include "ziparchive/zip_archive.h" using testing::Eq; using testing::Ne; @@ -53,7 +54,11 @@ TEST_F(ConvertTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag); - AssertLoadXml(apk.get(), "res/xml/test.xml", &tree); + + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); + ASSERT_THAT(data, Ne(nullptr)); + + AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has not been assigned EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1)); @@ -87,7 +92,11 @@ TEST_F(ConvertTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag); - AssertLoadXml(apk.get(), "res/xml/test.xml", &tree); + + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); + ASSERT_THAT(data, Ne(nullptr)); + + AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has been set to the correct string pool entry int32_t raw_index = tree.getAttributeValueStringID(0); @@ -95,4 +104,45 @@ TEST_F(ConvertTest, KeepRawXmlStrings) { EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); } +TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) { + StdErrDiagnostics diag; + const std::string apk_path = + file::BuildPath({android::base::GetExecutableDirectory(), + "integration-tests", "ConvertTest", "duplicate_entries.apk"}); + + const std::string out_convert_apk = GetTestPath("out_convert.apk"); + std::vector<android::StringPiece> convert_args = { + "-o", out_convert_apk, + "--output-format", "proto", + apk_path + }; + ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0)); + + ZipArchiveHandle handle; + ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0)); + + void* cookie = nullptr; + + ZipString prefix("res/theme/10"); + int32_t result = StartIteration(handle, &cookie, &prefix, nullptr); + + // If this is -5, that means we've found a duplicate entry and this test has failed + EXPECT_THAT(result, Eq(0)); + + // But if read succeeds, verify only one res/theme/10 entry + int count = 0; + + // Can't pass nullptrs into Next() + ZipString zip_name; + ZipEntry zip_data; + + while ((result = Next(cookie, &zip_data, &zip_name)) == 0) { + count++; + } + + EndIteration(cookie); + + EXPECT_THAT(count, Eq(1)); +} + } // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 3c8b72d3cb2c..9ea93f638aff 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -43,7 +43,11 @@ TEST_F(LinkTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - AssertLoadXml(apk.get(), "res/xml/test.xml", &tree); + + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); + ASSERT_THAT(data, Ne(nullptr)); + + AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has not been assigned EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1)); @@ -67,7 +71,11 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - AssertLoadXml(apk.get(), "res/xml/test.xml", &tree); + + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); + ASSERT_THAT(data, Ne(nullptr)); + + AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has been set to the correct string pool entry int32_t raw_index = tree.getAttributeValueStringID(0); diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk Binary files differnew file mode 100644 index 000000000000..c558a334b369 --- /dev/null +++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index aae79fafc0a6..3fcdfb70a524 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -133,16 +133,18 @@ std::string CommandTestFixture::GetDefaultManifest() { return manifest_file; } -void CommandTestFixture::AssertLoadXml(LoadedApk *apk, const android::StringPiece &xml_path, +std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk, + const android::StringPiece& path) { + return apk + ->GetFileCollection() + ->FindFile(path) + ->OpenAsData(); +} + +void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data, android::ResXMLTree *out_tree) { ASSERT_THAT(apk, Ne(nullptr)); - io::IFile* file = apk->GetFileCollection()->FindFile(xml_path); - ASSERT_THAT(file, Ne(nullptr)); - - std::unique_ptr<io::IData> data = file->OpenAsData(); - ASSERT_THAT(data, Ne(nullptr)); - out_tree->setTo(data->data(), data->size()); ASSERT_THAT(out_tree->getError(), Eq(android::OK)); while (out_tree->next() != android::ResXMLTree::START_TAG) { diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index 89d3b7b751a0..3079c757f61a 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -83,8 +83,12 @@ class CommandTestFixture : public TestDirectoryFixture { // Creates a minimal android manifest within the test directory and returns the file path. std::string GetDefaultManifest(); + // Returns pointer to data inside APK files + std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, + const android::StringPiece& path); + // Asserts that loading the tree from the specified file in the apk succeeds. - void AssertLoadXml(LoadedApk* apk, const android::StringPiece& xml_path, + void AssertLoadXml(LoadedApk* apk, const io::IData* data, android::ResXMLTree* out_tree); private: diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp index 961b47cdfecd..cee0cd52a546 100644 --- a/tools/bit/aapt.cpp +++ b/tools/bit/aapt.cpp @@ -159,10 +159,11 @@ int inspect_apk(Apk* apk, const string& filename) { // Load the manifest xml - Command cmd("aapt"); + Command cmd("aapt2"); cmd.AddArg("dump"); cmd.AddArg("xmltree"); cmd.AddArg(filename); + cmd.AddArg("--file"); cmd.AddArg("AndroidManifest.xml"); int err; @@ -217,11 +218,11 @@ inspect_apk(Apk* apk, const string& filename) if (current != NULL) { Attribute attr; string str = match[2]; - size_t colon = str.find(':'); + size_t colon = str.rfind(':'); if (colon == string::npos) { attr.name = str; } else { - attr.ns = scope->namespaces[string(str, 0, colon)]; + attr.ns.assign(str, 0, colon); attr.name.assign(str, colon+1, string::npos); } attr.value = match[3]; diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index a71cea1c44f9..860094aef1f4 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -623,12 +623,13 @@ run_phases(vector<Target*> targets, const Options& options) const string buildProduct = get_required_env("TARGET_PRODUCT", false); const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); const string buildType = get_required_env("TARGET_BUILD_TYPE", false); - + const string buildOut = get_out_dir(); chdir_or_exit(buildTop.c_str()); - const string buildDevice = get_build_var("TARGET_DEVICE", false); - const string buildId = get_build_var("BUILD_ID", false); - const string buildOut = get_out_dir(); + BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType); + + const string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false); + const string buildId = buildVars.GetBuildVar("BUILD_ID", false); // Get the modules for the targets map<string,Module> modules; @@ -661,6 +662,7 @@ run_phases(vector<Target*> targets, const Options& options) string dataPath = buildOut + "/target/product/" + buildDevice + "/data/"; bool syncSystem = false; bool alwaysSyncSystem = false; + vector<string> systemFiles; vector<InstallApk> installApks; for (size_t i=0; i<targets.size(); i++) { Target* target = targets[i]; @@ -670,6 +672,7 @@ run_phases(vector<Target*> targets, const Options& options) // System partition if (starts_with(file, systemPath)) { syncSystem = true; + systemFiles.push_back(file); if (!target->build) { // If a system partition target didn't get built then // it won't change we will always need to do adb sync @@ -692,6 +695,19 @@ run_phases(vector<Target*> targets, const Options& options) get_directory_contents(systemPath, &systemFilesBefore); } + if (systemFiles.size() > 0){ + print_info("System files:"); + for (size_t i=0; i<systemFiles.size(); i++) { + printf(" %s\n", systemFiles[i].c_str()); + } + } + if (installApks.size() > 0){ + print_info("APKs to install:"); + for (size_t i=0; i<installApks.size(); i++) { + printf(" %s\n", installApks[i].file.filename.c_str()); + } + } + // // Build // @@ -798,7 +814,8 @@ run_phases(vector<Target*> targets, const Options& options) for (size_t j=0; j<target->module.installed.size(); j++) { string filename = target->module.installed[j]; - if (!ends_with(filename, ".apk")) { + // Apk in the data partition + if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) { continue; } @@ -1004,13 +1021,16 @@ run_phases(vector<Target*> targets, const Options& options) void run_tab_completion(const string& word) { - const string buildTop = get_required_env("ANDROID_BUILD_TOP", true); + const string buildTop = get_required_env("ANDROID_BUILD_TOP", false); const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); + const string buildType = get_required_env("TARGET_BUILD_TYPE", false); const string buildOut = get_out_dir(); - chdir_or_exit(buildTop.c_str()); - string buildDevice = sniff_device_name(buildOut, buildProduct); + BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType); + + string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false); map<string,Module> modules; read_modules(buildOut, buildDevice, &modules, true); diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index 5a9ab22719cb..627091321b2e 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -21,6 +21,7 @@ #include "util.h" #include <json/reader.h> +#include <json/writer.h> #include <json/value.h> #include <fstream> @@ -34,22 +35,118 @@ using namespace std; -map<string,string> g_buildVars; +static bool +map_contains(const map<string,string>& m, const string& k, const string& v) { + map<string,string>::const_iterator it = m.find(k); + if (it == m.end()) { + return false; + } + return it->second == v; +} + +static string +make_cache_filename(const string& outDir) +{ + string filename(outDir); + return filename + "/.bit_cache"; +} + +BuildVars::BuildVars(const string& outDir, const string& buildProduct, + const string& buildVariant, const string& buildType) + :m_filename(), + m_cache() +{ + m_cache["TARGET_PRODUCT"] = buildProduct; + m_cache["TARGET_BUILD_VARIANT"] = buildVariant; + m_cache["TARGET_BUILD_TYPE"] = buildType; + + // If we have any problems reading the file, that's ok, just do + // uncached calls to make / soong. + + if (outDir == "") { + return; + } + + + m_filename = make_cache_filename(outDir); + + std::ifstream stream(m_filename, std::ifstream::binary); + + if (stream.fail()) { + return; + } + + Json::Value json; + Json::Reader reader; + if (!reader.parse(stream, json)) { + return; + } + + if (!json.isObject()) { + return; + } + + map<string,string> cache; + + vector<string> names = json.getMemberNames(); + const int N = names.size(); + for (int i=0; i<N; i++) { + const string& name = names[i]; + const Json::Value& value = json[name]; + if (!value.isString()) { + continue; + } + cache[name] = value.asString(); + } + + // If all of the base variables match, then we can use this cache. Otherwise, use our + // base one. The next time someone reads a value, the new one, with our base varaibles + // will be saved. + if (map_contains(cache, "TARGET_PRODUCT", buildProduct) + && map_contains(cache, "TARGET_BUILD_VARIANT", buildVariant) + && map_contains(cache, "TARGET_BUILD_TYPE", buildType)) { + m_cache = cache; + } +} + +BuildVars::~BuildVars() +{ +} + +void +BuildVars::save() +{ + if (m_filename == "") { + return; + } + + Json::StyledStreamWriter writer(" "); + + Json::Value json(Json::objectValue); + + for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) { + json[it->first] = it->second; + } + + std::ofstream stream(m_filename, std::ofstream::binary); + writer.write(stream, json); +} string -get_build_var(const string& name, bool quiet) +BuildVars::GetBuildVar(const string& name, bool quiet) { int err; - map<string,string>::iterator it = g_buildVars.find(name); - if (it == g_buildVars.end()) { + map<string,string>::iterator it = m_cache.find(name); + if (it == m_cache.end()) { Command cmd("build/soong/soong_ui.bash"); cmd.AddArg("--dumpvar-mode"); cmd.AddArg(name); string output = trim(get_command_output(cmd, &err, quiet)); if (err == 0) { - g_buildVars[name] = output; + m_cache[name] = output; + save(); return output; } else { return string(); @@ -59,38 +156,6 @@ get_build_var(const string& name, bool quiet) } } -string -sniff_device_name(const string& buildOut, const string& product) -{ - string match("ro.build.product=" + product); - - string base(buildOut + "/target/product"); - DIR* dir = opendir(base.c_str()); - if (dir == NULL) { - return string(); - } - - dirent* entry; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') { - continue; - } - if (entry->d_type == DT_DIR) { - string filename(base + "/" + entry->d_name + "/system/build.prop"); - vector<string> lines; - split_lines(&lines, read_file(filename)); - for (size_t i=0; i<lines.size(); i++) { - if (lines[i] == match) { - return entry->d_name; - } - } - } - } - - closedir(dir); - return string(); -} - void json_error(const string& filename, const char* error, bool quiet) { diff --git a/tools/bit/make.h b/tools/bit/make.h index 1c9504d62d46..db0b69f88a0e 100644 --- a/tools/bit/make.h +++ b/tools/bit/make.h @@ -31,16 +31,26 @@ struct Module vector<string> installed; }; -string get_build_var(const string& name, bool quiet); - /** - * Poke around in the out directory and try to find a device name that matches - * our product. This is faster than running get_build_var and good enough for - * tab completion. - * - * Returns the empty string if we can't find one. + * Class to encapsulate getting build variables. Caches the + * results if possible. */ -string sniff_device_name(const string& buildOut, const string& product); +class BuildVars +{ +public: + BuildVars(const string& outDir, const string& buildProduct, + const string& buildVariant, const string& buildType); + ~BuildVars(); + + string GetBuildVar(const string& name, bool quiet); + +private: + void save(); + + string m_filename; + + map<string,string> m_cache; +}; void read_modules(const string& buildOut, const string& buildDevice, map<string,Module>* modules, bool quiet); diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp index 790e0b4b227e..35feda11ec29 100644 --- a/tools/bit/print.cpp +++ b/tools/bit/print.cpp @@ -116,6 +116,20 @@ print_warning(const char* format, ...) } void +print_info(const char* format, ...) +{ + fputs(g_escapeBold, stdout); + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + + fputs(g_escapeEndColor, stdout); + fputc('\n', stdout); +} + +void print_one_line(const char* format, ...) { if (g_stdoutIsTty) { diff --git a/tools/bit/print.h b/tools/bit/print.h index b6c3e9aa27fa..db6cf5f65cf8 100644 --- a/tools/bit/print.h +++ b/tools/bit/print.h @@ -33,6 +33,7 @@ void print_status(const char* format, ...); void print_command(const Command& command); void print_error(const char* format, ...); void print_warning(const char* format, ...); +void print_info(const char* format, ...); void print_one_line(const char* format, ...); void check_error(int err); diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp index 1aca3edfab88..0e33fddcde07 100644 --- a/tools/processors/unsupportedappusage/Android.bp +++ b/tools/processors/unsupportedappusage/Android.bp @@ -1,6 +1,8 @@ -java_library_host { +java_plugin { name: "unsupportedappusage-annotation-processor", + processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor", + java_resources: [ "META-INF/**/*", ], diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp index 9b5df56e3987..06ff05e5d755 100644 --- a/tools/processors/view_inspector/Android.bp +++ b/tools/processors/view_inspector/Android.bp @@ -1,6 +1,8 @@ -java_library_host { +java_plugin { name: "view-inspector-annotation-processor", + processor_class: "android.processor.view.inspector.PlatformInspectableProcessor", + srcs: ["src/java/**/*.java"], java_resource_dirs: ["src/resources"], diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem index 0af577bf81e1..17a1dff71707 100644 --- a/tools/signedconfig/debug_key.pem +++ b/tools/signedconfig/debug_key.pem @@ -1,5 +1,5 @@ -----BEGIN EC PRIVATE KEY----- -MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49 -AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8 -SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ== +MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49 +AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1 +ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg== -----END EC PRIVATE KEY----- diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem index f61f81322b94..d9f0d387b823 100644 --- a/tools/signedconfig/debug_public.pem +++ b/tools/signedconfig/debug_public.pem @@ -1,4 +1,4 @@ -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE -CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ== +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr +U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg== -----END PUBLIC KEY----- diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh index 28e54289f8f8..3a2814a62aff 100755 --- a/tools/signedconfig/debug_sign.sh +++ b/tools/signedconfig/debug_sign.sh @@ -2,5 +2,5 @@ # Script to sign data with the debug keys. Outputs base64 for embedding into # APK metadata. -openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem $1 | base64 -w 0 +openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem <(echo -n "$1" | base64 -d) | base64 -w 0 echo diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 46c419130233..d5497990aefd 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -28,6 +28,7 @@ import android.net.wifi.IDppCallback; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ITrafficStateCallback; +import android.net.wifi.IWifiUsabilityStatsListener; import android.net.wifi.PasspointManagementObjectDefinition; import android.net.wifi.ScanResult; import android.net.wifi.WifiActivityEnergyInfo; @@ -186,6 +187,10 @@ interface IWifiManager void unregisterSoftApCallback(int callbackIdentifier); + void addWifiUsabilityStatsListener(in IBinder binder, in IWifiUsabilityStatsListener listener, int listenerIdentifier); + + void removeWifiUsabilityStatsListener(int listenerIdentifier); + void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier); void unregisterTrafficStateCallback(int callbackIdentifier); @@ -209,5 +214,6 @@ interface IWifiManager in IDppCallback callback); void stopDppSession(); -} + void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec); +} diff --git a/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl new file mode 100644 index 000000000000..284ffaa18257 --- /dev/null +++ b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.net.wifi.WifiUsabilityStatsEntry; + +/** + * Interface for Wi-Fi usability stats listener. + * + * @hide + */ +oneway interface IWifiUsabilityStatsListener +{ + /** + * Service to manager callback providing current Wi-Fi usability stats. + * + * @param seqNum The sequence number of stats, used to derive the timing of updated Wi-Fi + * usability statistics, set by framework and shall be incremented by one + * after each update. + * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of + * network stays the same or not relative to the last update of + * Wi-Fi usability stats. + * @param stats The updated Wi-Fi usability statistics. + */ + void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq, + in WifiUsabilityStatsEntry stats); +} diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index d2d711f10944..96493de69673 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -787,6 +787,18 @@ public class WifiConfiguration implements Parcelable { public boolean trusted; /** + * This Wifi configuration is created from a {@link WifiNetworkSuggestion} + * @hide + */ + public boolean fromWifiNetworkSuggestion; + + /** + * This Wifi configuration is created from a {@link WifiNetworkSpecifier} + * @hide + */ + public boolean fromWifiNetworkSpecifier; + + /** * Indicates if the creator of this configuration has expressed that it * should be considered metered. * @@ -1668,6 +1680,8 @@ public class WifiConfiguration implements Parcelable { ephemeral = false; osu = false; trusted = true; // Networks are considered trusted by default. + fromWifiNetworkSuggestion = false; + fromWifiNetworkSpecifier = false; meteredHint = false; meteredOverride = METERED_OVERRIDE_NONE; useExternalScores = false; @@ -1779,10 +1793,13 @@ public class WifiConfiguration implements Parcelable { if (this.ephemeral) sbuf.append(" ephemeral"); if (this.osu) sbuf.append(" osu"); if (this.trusted) sbuf.append(" trusted"); + if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion"); + if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier"); if (this.meteredHint) sbuf.append(" meteredHint"); if (this.useExternalScores) sbuf.append(" useExternalScores"); if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess - || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) { + || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion + || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) { sbuf.append("\n"); } if (this.meteredOverride != METERED_OVERRIDE_NONE) { @@ -2270,6 +2287,8 @@ public class WifiConfiguration implements Parcelable { ephemeral = source.ephemeral; osu = source.osu; trusted = source.trusted; + fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion; + fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier; meteredHint = source.meteredHint; meteredOverride = source.meteredOverride; useExternalScores = source.useExternalScores; @@ -2347,6 +2366,8 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(isLegacyPasspointConfig ? 1 : 0); dest.writeInt(ephemeral ? 1 : 0); dest.writeInt(trusted ? 1 : 0); + dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0); + dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0); dest.writeInt(meteredHint ? 1 : 0); dest.writeInt(meteredOverride); dest.writeInt(useExternalScores ? 1 : 0); @@ -2418,6 +2439,8 @@ public class WifiConfiguration implements Parcelable { config.isLegacyPasspointConfig = in.readInt() != 0; config.ephemeral = in.readInt() != 0; config.trusted = in.readInt() != 0; + config.fromWifiNetworkSuggestion = in.readInt() != 0; + config.fromWifiNetworkSpecifier = in.readInt() != 0; config.meteredHint = in.readInt() != 0; config.meteredOverride = in.readInt(); config.useExternalScores = in.readInt() != 0; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 35fba3dcf7cf..488de8789178 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.net.NetworkInfo.DetailedState; @@ -120,8 +121,14 @@ public class WifiInfo implements Parcelable { @UnsupportedAppUsage private String mMacAddress = DEFAULT_MAC_ADDRESS; + /** + * Whether the network is ephemeral or not. + */ private boolean mEphemeral; + /** + * Whether the network is trusted or not. + */ private boolean mTrusted; /** @@ -130,6 +137,12 @@ public class WifiInfo implements Parcelable { private boolean mOsuAp; /** + * If connected to a network suggestion or specifier, store the package name of the app, + * else null. + */ + private String mNetworkSuggestionOrSpecifierPackageName; + + /** * Running total count of lost (not ACKed) transmitted unicast data packets. * @hide */ @@ -209,6 +222,7 @@ public class WifiInfo implements Parcelable { setMeteredHint(false); setEphemeral(false); setOsuAp(false); + setNetworkSuggestionOrSpecifierPackageName(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -240,6 +254,8 @@ public class WifiInfo implements Parcelable { mMeteredHint = source.mMeteredHint; mEphemeral = source.mEphemeral; mTrusted = source.mTrusted; + mNetworkSuggestionOrSpecifierPackageName = + source.mNetworkSuggestionOrSpecifierPackageName; mOsuAp = source.mOsuAp; txBad = source.txBad; txRetries = source.txRetries; @@ -476,6 +492,17 @@ public class WifiInfo implements Parcelable { return mOsuAp; } + /** {@hide} */ + public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) { + mNetworkSuggestionOrSpecifierPackageName = packageName; + } + + /** {@hide} */ + public @Nullable String getNetworkSuggestionOrSpecifierPackageName() { + return mNetworkSuggestionOrSpecifierPackageName; + } + + /** @hide */ @UnsupportedAppUsage public void setNetworkId(int id) { @@ -634,6 +661,7 @@ public class WifiInfo implements Parcelable { dest.writeDouble(rxSuccessRate); mSupplicantState.writeToParcel(dest, flags); dest.writeInt(mOsuAp ? 1 : 0); + dest.writeString(mNetworkSuggestionOrSpecifierPackageName); } /** Implement the Parcelable interface {@hide} */ @@ -672,6 +700,7 @@ public class WifiInfo implements Parcelable { info.rxSuccessRate = in.readDouble(); info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); info.mOsuAp = in.readInt() != 0; + info.mNetworkSuggestionOrSpecifierPackageName = in.readString(); return info; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1fd45e72f1e8..066823931832 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -16,7 +16,7 @@ package android.net.wifi; -import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.ACCESS_WIFI_STATE; import static android.Manifest.permission.READ_WIFI_CREDENTIAL; @@ -950,8 +950,7 @@ public class WifiManager { * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag * set. * <p> - * Note: The broadcast is sent to the app only if it holds either one of - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or + * Note: The broadcast is sent to the app only if it holds * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission. * * @see #EXTRA_NETWORK_SUGGESTION @@ -1183,7 +1182,7 @@ public class WifiManager { * containing configurations which they created. */ @Deprecated - @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE}) + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) public List<WifiConfiguration> getConfiguredNetworks() { try { ParceledListSlice<WifiConfiguration> parceledList = @@ -1199,7 +1198,7 @@ public class WifiManager { /** @hide */ @SystemApi - @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL}) + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL}) public List<WifiConfiguration> getPrivilegedConfiguredNetworks() { try { ParceledListSlice<WifiConfiguration> parceledList = @@ -1405,7 +1404,6 @@ public class WifiManager { * {@link #reject()} to return the user's selection back to the platform via this callback. * @hide */ - @SystemApi public interface NetworkRequestUserSelectionCallback { /** * User selected this network to connect to. @@ -1429,7 +1427,6 @@ public class WifiManager { * or reject the request by the app. * @hide */ - @SystemApi public interface NetworkRequestMatchCallback { /** * Invoked to register a callback to be invoked to convey user selection. The callback @@ -1606,7 +1603,6 @@ public class WifiManager { * object. If null, then the application's main thread will be used. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback, @Nullable Handler handler) { @@ -1636,7 +1632,6 @@ public class WifiManager { * @param callback Callback for network match events * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback( @NonNull NetworkRequestMatchCallback callback) { @@ -1656,8 +1651,7 @@ public class WifiManager { * When the device decides to connect to one of the provided network suggestions, platform sends * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if * the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} - * flag set and the app holds either one of - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or + * flag set and the app holds * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission. *<p> * NOTE: @@ -2290,7 +2284,6 @@ public class WifiManager { /** * Return the results of the latest access point scan. * @return the list of access points found in the most recent scan. An app must hold - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission * in order to get valid results. */ @@ -2601,7 +2594,7 @@ public class WifiManager { * <p> * Applications need to have the following permissions to start LocalOnlyHotspot: {@link * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link - * android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}. Callers without + * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. Callers without * the permissions will trigger a {@link java.lang.SecurityException}. * <p> * @param callback LocalOnlyHotspotCallback for the application to receive updates about @@ -2684,7 +2677,7 @@ public class WifiManager { * {@link LocalOnlyHotspotObserver#onStopped()} callbacks. * <p> * Applications should have the - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} * permission. Callers without the permission will trigger a * {@link java.lang.SecurityException}. * <p> @@ -4777,4 +4770,114 @@ public class WifiManager { }); } } -} + + /** + * Interface for Wi-Fi usability statistics listener. Should be implemented by applications and + * set when calling {@link WifiManager#addWifiUsabilityStatsListener(Executor, + * WifiUsabilityStatsListener)}. + * + * @hide + */ + @SystemApi + public interface WifiUsabilityStatsListener { + /** + * Called when Wi-Fi usability statistics is updated. + * + * @param seqNum The sequence number of statistics, used to derive the timing of updated + * Wi-Fi usability statistics, set by framework and incremented by one after + * each update. + * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of + * network stays the same or not relative to the last update of + * Wi-Fi usability stats. + * @param stats The updated Wi-Fi usability statistics. + */ + void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq, + WifiUsabilityStatsEntry stats); + } + + /** + * Adds a listener for Wi-Fi usability statistics. See {@link WifiUsabilityStatsListener}. + * Multiple listeners can be added. Callers will be invoked periodically by framework to + * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously + * added listener using {@link removeWifiUsabilityStatsListener}. + * + * @param executor The executor on which callback will be invoked. + * @param listener Listener for Wifi usability statistics. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) + public void addWifiUsabilityStatsListener(@NonNull @CallbackExecutor Executor executor, + @NonNull WifiUsabilityStatsListener listener) { + if (executor == null) throw new IllegalArgumentException("executor cannot be null"); + if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "addWifiUsabilityStatsListener: listener=" + listener); + } + try { + mService.addWifiUsabilityStatsListener(new Binder(), + new IWifiUsabilityStatsListener.Stub() { + @Override + public void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq, + WifiUsabilityStatsEntry stats) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "WifiUsabilityStatsListener: onStatsUpdated: seqNum=" + + seqNum); + } + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> listener.onStatsUpdated(seqNum, + isSameBssidAndFreq, stats))); + } + }, + listener.hashCode() + ); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Allow callers to remove a previously registered listener. After calling this method, + * applications will no longer receive Wi-Fi usability statistics. + * + * @param listener Listener to remove the Wi-Fi usability statistics. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) + public void removeWifiUsabilityStatsListener(@NonNull WifiUsabilityStatsListener listener) { + if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "removeWifiUsabilityStatsListener: listener=" + listener); + } + try { + mService.removeWifiUsabilityStatsListener(listener.hashCode()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the + * framework. The Wi-Fi usability score is derived from {@link WifiUsabilityStatsListener} + * where a score is matched to Wi-Fi usability statistics using the sequence number. The score + * is used to quantify whether Wi-Fi is usable in a future time. + * + * @param seqNum Sequence number of the Wi-Fi usability score. + * @param score The Wi-Fi usability score. + * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) + public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { + try { + mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl new file mode 100644 index 000000000000..839af54b81ba --- /dev/null +++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable WifiUsabilityStatsEntry; diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java new file mode 100644 index 000000000000..c796e29e4e1a --- /dev/null +++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class makes a subset of + * com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStatsEntry parcelable. + * + * @hide + */ +@SystemApi +public final class WifiUsabilityStatsEntry implements Parcelable { + /** Absolute milliseconds from device boot when these stats were sampled */ + public final long timeStampMs; + /** The RSSI (in dBm) at the sample time */ + public final int rssi; + /** Link speed at the sample time in Mbps */ + public final int linkSpeedMbps; + /** The total number of tx success counted from the last radio chip reset */ + public final long totalTxSuccess; + /** The total number of MPDU data packet retries counted from the last radio chip reset */ + public final long totalTxRetries; + /** The total number of tx bad counted from the last radio chip reset */ + public final long totalTxBad; + /** The total number of rx success counted from the last radio chip reset */ + public final long totalRxSuccess; + /** The total time the wifi radio is on in ms counted from the last radio chip reset */ + public final long totalRadioOnTimeMs; + /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */ + public final long totalRadioTxTimeMs; + /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */ + public final long totalRadioRxTimeMs; + /** The total time spent on all types of scans in ms counted from the last radio chip reset */ + public final long totalScanTimeMs; + /** The total time spent on nan scans in ms counted from the last radio chip reset */ + public final long totalNanScanTimeMs; + /** The total time spent on background scans in ms counted from the last radio chip reset */ + public final long totalBackgroundScanTimeMs; + /** The total time spent on roam scans in ms counted from the last radio chip reset */ + public final long totalRoamScanTimeMs; + /** The total time spent on pno scans in ms counted from the last radio chip reset */ + public final long totalPnoScanTimeMs; + /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio + * chip reset */ + public final long totalHotspot2ScanTimeMs; + /** The total time CCA is on busy status on the current frequency in ms counted from the last + * radio chip reset */ + public final long totalCcaBusyFreqTimeMs; + /** The total radio on time of the current frequency from the last radio chip reset */ + public final long totalRadioOnFreqTimeMs; + /** The total number of beacons received from the last radio chip reset */ + public final long totalBeaconRx; + + /** Constructor function {@hide} */ + public WifiUsabilityStatsEntry(long timeStampMs, int rssi, + int linkSpeedMbps, long totalTxSuccess, long totalTxRetries, + long totalTxBad, long totalRxSuccess, long totalRadioOnTimeMs, + long totalRadioTxTimeMs, long totalRadioRxTimeMs, long totalScanTimeMs, + long totalNanScanTimeMs, long totalBackgroundScanTimeMs, long totalRoamScanTimeMs, + long totalPnoScanTimeMs, long totalHotspot2ScanTimeMs, long totalCcaBusyFreqTimeMs, + long totalRadioOnFreqTimeMs, long totalBeaconRx) { + this.timeStampMs = timeStampMs; + this.rssi = rssi; + this.linkSpeedMbps = linkSpeedMbps; + this.totalTxSuccess = totalTxSuccess; + this.totalTxRetries = totalTxRetries; + this.totalTxBad = totalTxBad; + this.totalRxSuccess = totalRxSuccess; + this.totalRadioOnTimeMs = totalRadioOnTimeMs; + this.totalRadioTxTimeMs = totalRadioTxTimeMs; + this.totalRadioRxTimeMs = totalRadioRxTimeMs; + this.totalScanTimeMs = totalScanTimeMs; + this.totalNanScanTimeMs = totalNanScanTimeMs; + this.totalBackgroundScanTimeMs = totalBackgroundScanTimeMs; + this.totalRoamScanTimeMs = totalRoamScanTimeMs; + this.totalPnoScanTimeMs = totalPnoScanTimeMs; + this.totalHotspot2ScanTimeMs = totalHotspot2ScanTimeMs; + this.totalCcaBusyFreqTimeMs = totalCcaBusyFreqTimeMs; + this.totalRadioOnFreqTimeMs = totalRadioOnFreqTimeMs; + this.totalBeaconRx = totalBeaconRx; + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(timeStampMs); + dest.writeInt(rssi); + dest.writeInt(linkSpeedMbps); + dest.writeLong(totalTxSuccess); + dest.writeLong(totalTxRetries); + dest.writeLong(totalTxBad); + dest.writeLong(totalRxSuccess); + dest.writeLong(totalRadioOnTimeMs); + dest.writeLong(totalRadioTxTimeMs); + dest.writeLong(totalRadioRxTimeMs); + dest.writeLong(totalScanTimeMs); + dest.writeLong(totalNanScanTimeMs); + dest.writeLong(totalBackgroundScanTimeMs); + dest.writeLong(totalRoamScanTimeMs); + dest.writeLong(totalPnoScanTimeMs); + dest.writeLong(totalHotspot2ScanTimeMs); + dest.writeLong(totalCcaBusyFreqTimeMs); + dest.writeLong(totalRadioOnFreqTimeMs); + dest.writeLong(totalBeaconRx); + } + + /** Implement the Parcelable interface */ + public static final Creator<WifiUsabilityStatsEntry> CREATOR = + new Creator<WifiUsabilityStatsEntry>() { + public WifiUsabilityStatsEntry createFromParcel(Parcel in) { + return new WifiUsabilityStatsEntry( + in.readLong(), in.readInt(), + in.readInt(), in.readLong(), in.readLong(), + in.readLong(), in.readLong(), in.readLong(), + in.readLong(), in.readLong(), in.readLong(), + in.readLong(), in.readLong(), in.readLong(), + in.readLong(), in.readLong(), in.readLong(), + in.readLong(), in.readLong() + ); + } + + public WifiUsabilityStatsEntry[] newArray(int size) { + return new WifiUsabilityStatsEntry[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java index 81a06e825439..a8b19b3e2f64 100644 --- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java +++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java @@ -30,7 +30,7 @@ package android.net.wifi.aware; public class IdentityChangedListener { /** * @param mac The MAC address of the Aware discovery interface. The application must have the - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address, + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address, * otherwise all 0's will be provided. */ public void onIdentityChanged(byte[] mac) { diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 1fa1fd521a8e..8aef7a2cc6b6 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -231,7 +231,7 @@ public class WifiAwareManager { * <p> * This version of the API attaches a listener to receive the MAC address of the Aware interface * on startup and whenever it is updated (it is randomized at regular intervals for privacy). - * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to execute this attach request. Otherwise, use the * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission * requirements this listener will wake up the host at regular intervals causing higher power diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java index 5f8841cb0148..245b3043d30a 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java @@ -133,7 +133,7 @@ public class WifiAwareSession implements AutoCloseable { * An application must use the {@link DiscoverySession#close()} to * terminate the publish discovery session once it isn't needed. This will free * resources as well terminate any on-air transmissions. - * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to start a publish discovery session. * * @param publishConfig The {@link PublishConfig} specifying the @@ -179,7 +179,7 @@ public class WifiAwareSession implements AutoCloseable { * An application must use the {@link DiscoverySession#close()} to * terminate the subscribe discovery session once it isn't needed. This will free * resources as well terminate any on-air transmissions. - * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to start a subscribe discovery session. * * @param subscribeConfig The {@link SubscribeConfig} specifying the diff --git a/wifi/java/android/net/wifi/aware/package.html b/wifi/java/android/net/wifi/aware/package.html index d5d962f629de..c4f2e1fec96b 100644 --- a/wifi/java/android/net/wifi/aware/package.html +++ b/wifi/java/android/net/wifi/aware/package.html @@ -15,7 +15,7 @@ <ul> <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li> <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li> - <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li> + <li>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</li> </ul> <p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi Aware diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 1bed914c7772..052ab99da905 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -1139,7 +1139,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener)); @@ -1183,7 +1183,7 @@ public class WifiP2pManager { * @param config options as described in {@link WifiP2pConfig} class * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(Channel c, WifiP2pConfig config, ActionListener listener) { checkChannel(c); checkP2pConfig(config); @@ -1225,7 +1225,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID, @@ -1256,7 +1256,7 @@ public class WifiP2pManager { * @param config the configuration of a p2p group. * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull Channel c, @Nullable WifiP2pConfig config, @Nullable ActionListener listener) { @@ -1344,7 +1344,7 @@ public class WifiP2pManager { * @param servInfo is a local service information. * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) { checkChannel(c); checkServiceInfo(servInfo); @@ -1454,7 +1454,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener)); @@ -1530,7 +1530,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callback when peer list is available. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(Channel c, PeerListListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener)); @@ -1553,7 +1553,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callback when group info is available. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(Channel c, GroupInfoListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener)); diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index b3ac9f15eb9a..c236c7a05488 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -26,6 +26,7 @@ import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiManager; +import android.net.wifi.IWifiUsabilityStatsListener; import android.net.wifi.ScanResult; import android.net.wifi.WifiActivityEnergyInfo; import android.net.wifi.WifiConfiguration; @@ -464,4 +465,20 @@ public class BaseWifiService extends IWifiManager.Stub { public void stopDppSession() throws RemoteException { throw new UnsupportedOperationException(); } + + @Override + public void addWifiUsabilityStatsListener( + IBinder binder, IWifiUsabilityStatsListener listener, int listenerIdentifier) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeWifiUsabilityStatsListener(int listenerIdentifier) { + throw new UnsupportedOperationException(); + } + + @Override + public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 7bff68aaaa97..449423f44a35 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -60,6 +60,8 @@ public class WifiConfigurationTest { config.setPasspointManagementObjectTree(cookie); config.trusted = false; config.updateIdentifier = "1234"; + config.fromWifiNetworkSpecifier = true; + config.fromWifiNetworkSuggestion = true; MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress(); Parcel parcelW = Parcel.obtain(); config.writeToParcel(parcelW, 0); @@ -76,6 +78,8 @@ public class WifiConfigurationTest { assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress()); assertEquals(config.updateIdentifier, reconfig.updateIdentifier); assertFalse(reconfig.trusted); + assertTrue(config.fromWifiNetworkSpecifier); + assertTrue(config.fromWifiNetworkSuggestion); Parcel parcelWW = Parcel.obtain(); reconfig.writeToParcel(parcelWW, 0); diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index 677bf371c781..948dcfa47f59 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -35,6 +35,7 @@ public class WifiInfoTest { private static final long TEST_TX_RETRIES = 2; private static final long TEST_TX_BAD = 3; private static final long TEST_RX_SUCCESS = 4; + private static final String TEST_PACKAGE_NAME = "com.test.example"; /** * Verify parcel write/read with WifiInfo. @@ -48,6 +49,7 @@ public class WifiInfoTest { writeWifiInfo.rxSuccess = TEST_RX_SUCCESS; writeWifiInfo.setTrusted(true); writeWifiInfo.setOsuAp(true); + writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME); Parcel parcel = Parcel.obtain(); writeWifiInfo.writeToParcel(parcel, 0); @@ -62,5 +64,6 @@ public class WifiInfoTest { assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess); assertTrue(readWifiInfo.isTrusted()); assertTrue(readWifiInfo.isOsuAp()); + assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName()); } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 4fbef5a8493d..5c2f626a24cc 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -61,6 +61,7 @@ import android.net.wifi.WifiManager.NetworkRequestMatchCallback; import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback; import android.net.wifi.WifiManager.SoftApCallback; import android.net.wifi.WifiManager.TrafficStateCallback; +import android.net.wifi.WifiManager.WifiUsabilityStatsListener; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -80,6 +81,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * Unit tests for {@link android.net.wifi.WifiManager}. @@ -103,7 +105,9 @@ public class WifiManagerTest { @Mock SoftApCallback mSoftApCallback; @Mock TrafficStateCallback mTrafficStateCallback; @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback; + @Mock WifiUsabilityStatsListener mWifiUsabilityStatsListener; + private Executor mExecutor; private Handler mHandler; private TestLooper mLooper; private WifiManager mWifiManager; @@ -1342,4 +1346,40 @@ i * Verify that a call to cancel WPS immediately returns a failure. assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses()); verify(mWifiService).getFactoryMacAddresses(); } + + /** + * Verify the call to addWifiUsabilityStatsListener goes to WifiServiceImpl. + */ + @Test + public void addWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception { + mExecutor = new SynchronousExecutor(); + mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener); + verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class), + any(IWifiUsabilityStatsListener.Stub.class), anyInt()); + } + + /** + * Verify the call to removeWifiUsabilityStatsListener goes to WifiServiceImpl. + */ + @Test + public void removeWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception { + ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class); + mExecutor = new SynchronousExecutor(); + mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener); + verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class), + any(IWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture()); + + mWifiManager.removeWifiUsabilityStatsListener(mWifiUsabilityStatsListener); + verify(mWifiService).removeWifiUsabilityStatsListener( + eq((int) listenerIdentifier.getValue())); + } + + /** + * Defined for testing purpose. + */ + class SynchronousExecutor implements Executor { + public void execute(Runnable r) { + r.run(); + } + } } diff --git a/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java new file mode 100644 index 000000000000..a947b5568a16 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.validateMockitoUsage; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + + +/** + * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}. + */ +@SmallTest +public class WifiUsabilityStatsEntryTest { + + /** + * Setup before tests. + */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + /** + * Clean up after tests. + */ + @After + public void cleanup() { + validateMockitoUsage(); + } + + /** + * Verify parcel read/write for Wifi usability stats result. + */ + @Test + public void verifyStatsResultWriteAndThenRead() throws Exception { + WifiUsabilityStatsEntry writeResult = createResult(); + WifiUsabilityStatsEntry readResult = parcelWriteRead(writeResult); + assertWifiUsabilityStatsEntryEquals(writeResult, readResult); + } + + /** + * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it. + */ + private static WifiUsabilityStatsEntry parcelWriteRead( + WifiUsabilityStatsEntry writeResult) throws Exception { + Parcel parcel = Parcel.obtain(); + writeResult.writeToParcel(parcel, 0); + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + return WifiUsabilityStatsEntry.CREATOR.createFromParcel(parcel); + } + + private static WifiUsabilityStatsEntry createResult() { + return new WifiUsabilityStatsEntry( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 + ); + } + + private static void assertWifiUsabilityStatsEntryEquals( + WifiUsabilityStatsEntry expected, + WifiUsabilityStatsEntry actual) { + assertEquals(expected.timeStampMs, actual.timeStampMs); + assertEquals(expected.rssi, actual.rssi); + assertEquals(expected.linkSpeedMbps, actual.linkSpeedMbps); + assertEquals(expected.totalTxSuccess, actual.totalTxSuccess); + assertEquals(expected.totalTxRetries, actual.totalTxRetries); + assertEquals(expected.totalTxBad, actual.totalTxBad); + assertEquals(expected.totalRxSuccess, actual.totalRxSuccess); + assertEquals(expected.totalRadioOnTimeMs, actual.totalRadioOnTimeMs); + assertEquals(expected.totalRadioTxTimeMs, actual.totalRadioTxTimeMs); + assertEquals(expected.totalRadioRxTimeMs, actual.totalRadioRxTimeMs); + assertEquals(expected.totalScanTimeMs, actual.totalScanTimeMs); + assertEquals(expected.totalNanScanTimeMs, actual.totalNanScanTimeMs); + assertEquals(expected.totalBackgroundScanTimeMs, actual.totalBackgroundScanTimeMs); + assertEquals(expected.totalRoamScanTimeMs, actual.totalRoamScanTimeMs); + assertEquals(expected.totalPnoScanTimeMs, actual.totalPnoScanTimeMs); + assertEquals(expected.totalHotspot2ScanTimeMs, actual.totalHotspot2ScanTimeMs); + assertEquals(expected.totalCcaBusyFreqTimeMs, actual.totalCcaBusyFreqTimeMs); + assertEquals(expected.totalRadioOnFreqTimeMs, actual.totalRadioOnFreqTimeMs); + assertEquals(expected.totalBeaconRx, actual.totalBeaconRx); + } +} |