diff options
475 files changed, 18312 insertions, 4928 deletions
diff --git a/Android.bp b/Android.bp index b68312b47c46..a5cc89cd0ea1 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,6 +726,8 @@ java_defaults { "ext", ], + jarjar_rules: ":framework-hidl-jarjar", + static_libs: [ "apex_aidl_interface-java", "networkstack-aidl-interfaces-java", @@ -749,6 +745,7 @@ 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.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", @@ -791,6 +788,11 @@ filegroup { ], } +filegroup { + name: "framework-hidl-jarjar", + srcs: ["jarjar_rules_hidl.txt"], +} + java_library { name: "framework", defaults: ["framework-defaults"], @@ -801,11 +803,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 @@ -1264,7 +1262,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", ], @@ -1326,7 +1324,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, @@ -1772,6 +1770,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", @@ -1780,6 +1779,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/api/current.txt b/api/current.txt index 0ea7ecc8d6b1..c0a9c48266e6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5793,6 +5793,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); @@ -6578,7 +6579,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); @@ -6600,6 +6600,7 @@ package android.app.admin { method @Nullable public String[] getAccountTypesWithManagementDisabled(); method @Nullable public java.util.List<android.content.ComponentName> getActiveAdmins(); method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName); + method public java.util.List<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName); method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName); method @WorkerThread @NonNull public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String); method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName); @@ -6608,7 +6609,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); @@ -6674,6 +6675,7 @@ package android.app.admin { method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(@NonNull android.content.ComponentName); method public boolean isAffiliatedUser(); + method public boolean isAlwaysOnVpnLockdownEnabled(@NonNull android.content.ComponentName); method public boolean isApplicationHidden(@NonNull android.content.ComponentName, String); method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName); method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage(); @@ -6698,7 +6700,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); @@ -6712,6 +6713,7 @@ package android.app.admin { method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean); method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>); method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; + method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.List<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException; method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean); method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle); method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -6720,6 +6722,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>); @@ -6787,7 +6790,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"; @@ -6931,6 +6934,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 { @@ -8610,6 +8614,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); @@ -8705,6 +8716,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 @@ -10213,6 +10225,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"; @@ -11210,6 +11223,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(); @@ -11237,6 +11251,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); @@ -11433,6 +11455,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(); @@ -11675,6 +11698,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"; @@ -17078,6 +17102,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; @@ -24639,6 +24664,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; @@ -25408,6 +25434,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(); @@ -28469,6 +28496,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(); @@ -28973,6 +29001,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(); @@ -29183,6 +29234,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[]); @@ -35249,11 +35301,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 { @@ -38194,6 +38251,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 { @@ -38400,28 +38459,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 { @@ -38473,24 +38532,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 { @@ -39071,6 +39130,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"; @@ -41332,6 +41392,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); @@ -41378,6 +41454,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(); @@ -41458,6 +41552,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(); @@ -41476,6 +41572,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); @@ -41670,6 +41797,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); @@ -41679,9 +41807,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 { @@ -43042,6 +43176,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"; } @@ -43647,6 +43790,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); @@ -43894,7 +44038,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"; @@ -44658,6 +44804,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); @@ -44708,6 +44855,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 { @@ -52620,6 +52769,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); } @@ -52636,12 +52786,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 { @@ -52980,8 +53130,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); @@ -53078,6 +53228,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); } @@ -53098,7 +53258,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); @@ -53335,6 +53495,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); @@ -53412,7 +53573,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(); @@ -53425,6 +53586,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 @@ -53459,7 +53621,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); @@ -53470,6 +53632,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 { @@ -56846,7 +57009,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(); diff --git a/api/system-current.txt b/api/system-current.txt index 15d6ab7b63fc..fe2b4d9e262f 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 } @@ -1052,6 +1053,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 { @@ -1112,6 +1114,7 @@ package android.app.usage { 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); @@ -1119,6 +1122,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); @@ -1147,7 +1151,7 @@ package android.bluetooth { field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } - public abstract class BluetoothAdapter.MetadataListener { + public abstract static class BluetoothAdapter.MetadataListener { ctor public BluetoothAdapter.MetadataListener(); method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); } @@ -1155,14 +1159,18 @@ package android.bluetooth { 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 @@ -1804,9 +1812,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 @@ -3394,6 +3409,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 { @@ -3569,6 +3593,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(); @@ -4008,6 +4036,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(); @@ -4028,6 +4057,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; @@ -4060,6 +4093,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(); @@ -4070,9 +4104,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(); @@ -4090,6 +4127,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); @@ -4144,6 +4183,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 @@ -4277,6 +4317,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); @@ -4325,6 +4366,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 @@ -4596,6 +4651,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); @@ -4611,6 +4667,7 @@ package android.net.wifi { 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); @@ -4620,6 +4677,7 @@ package android.net.wifi { 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 @@ -4668,6 +4726,10 @@ package android.net.wifi { 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 { ctor public WifiNetworkConnectionStatistics(int, int); ctor public WifiNetworkConnectionStatistics(); @@ -4797,6 +4859,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 { @@ -5552,6 +5639,30 @@ 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); } @@ -5743,6 +5854,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 @@ -6018,10 +6130,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 { @@ -6035,7 +6144,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 { @@ -6261,77 +6369,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); @@ -7572,10 +7609,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 @@ -8648,12 +8688,20 @@ 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 @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); + method @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); - 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 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); + method @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 { @@ -9080,9 +9128,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 1f894c27d5ea..8e638fde6cc1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -14,6 +14,10 @@ package android { 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 +336,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 { @@ -802,11 +807,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(); @@ -814,7 +824,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(); @@ -826,6 +839,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); @@ -843,6 +858,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 @@ -929,6 +945,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); @@ -977,6 +994,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 @@ -1273,15 +1304,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; } @@ -1688,10 +1715,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 { @@ -1705,7 +1729,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 { @@ -1729,84 +1752,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); @@ -2341,6 +2294,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..9c320d3e2b03 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1097,6 +1097,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..fe0504fc034f 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -205,6 +205,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 60b2e259d729..8fb01b4a17c7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -34,6 +34,7 @@ import "frameworks/base/core/proto/android/server/connectivity/data_stall_event. 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"; @@ -206,6 +207,8 @@ message Atom { 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; } // Pulled events will start at field 10000. @@ -3018,8 +3021,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; @@ -4521,3 +4524,28 @@ message AdbConnectionChanged { // 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; +} 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..7e56beeefbf6 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}, 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..ee2fe8f5750f 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -3518,7 +3518,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/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/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 75c90542ebce..181accea2e6a 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -101,6 +101,12 @@ public class KeyguardManager { public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION"; /** + * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}. + * @hide + */ + public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT"; + + /** * A CharSequence description to show to the user on the alternate button when used with * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}. * @hide @@ -123,14 +129,39 @@ public class KeyguardManager { * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. * + * @param title Title to be shown on the dialog. + * @param description Description to be shown on the dialog. * @return the intent for launching the activity or null if no password is required. **/ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { - if (!isDeviceSecure()) return null; + return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */); + } + + /** + * 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 + * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. + * + * @param title Title to be shown on the dialog. + * @param description Description to be shown on the dialog. + * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke + * {@link android.hardware.biometrics.BiometricPrompt} with + * {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation( + * boolean)} set to false. + * @return the intent for launching the activity or null if no password is required. + * @hide + */ + public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description, + boolean useImplicit) { + if (!isDeviceSecure()) { + return null; + } Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); intent.putExtra(EXTRA_TITLE, title); intent.putExtra(EXTRA_DESCRIPTION, description); + intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit); // explicitly set the package for security intent.setPackage(getSettingsPackageForIntent(intent)); 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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 55a3acb16b11..8ca3544b8d67 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3291,7 +3291,7 @@ public class DevicePolicyManager { */ public int getPasswordMaximumLength(int quality) { PackageManager pm = mContext.getPackageManager(); - if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) { + if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) { return 0; } // Kind-of arbitrary. @@ -3980,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 @@ -3991,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"); @@ -4013,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()); } /** @@ -4033,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); @@ -5049,11 +5059,16 @@ public class DevicePolicyManager { } /** + * Service-specific error code used in implementation of {@code setAlwaysOnVpnPackage} methods. + * @hide + */ + public static final int ERROR_VPN_PACKAGE_NOT_FOUND = 1; + + /** * Called by a device or profile owner to configure an always-on VPN connection through a * specific application for the current user. This connection is automatically granted and * persisted after a reboot. - * <p> - * To support the always-on feature, an app must + * <p> To support the always-on feature, an app must * <ul> * <li>declare a {@link android.net.VpnService} in its manifest, guarded by * {@link android.Manifest.permission#BIND_VPN_SERVICE};</li> @@ -5062,12 +5077,13 @@ public class DevicePolicyManager { * {@link android.net.VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}.</li> * </ul> * The call will fail if called with the package name of an unsupported VPN app. + * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure + * of the VPN provider could break networking for all apps. * * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to * remove an existing always-on VPN configuration. * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. This carries the risk that any failure of the VPN provider - * could break networking for all apps. This has no effect when clearing. + * {@code false} otherwise. This has no effect when clearing. * @throws SecurityException if {@code admin} is not a device or a profile owner. * @throws NameNotFoundException if {@code vpnPackage} is not installed. * @throws UnsupportedOperationException if {@code vpnPackage} exists but does not support being @@ -5076,11 +5092,46 @@ public class DevicePolicyManager { public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage, boolean lockdownEnabled) throws NameNotFoundException, UnsupportedOperationException { + setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptyList()); + } + + /** + * A version of {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} that allows the + * admin to specify a set of apps that should be able to access the network directly when VPN + * is not connected. When VPN connects these apps switch over to VPN if allowed to use that VPN. + * System apps can always bypass VPN. + * <p> Note that the system doesn't update the whitelist when packages are installed or + * uninstalled, the admin app must call this method to keep the list up to date. + * + * @param vpnPackage package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. This has no effect when clearing. + * @param lockdownWhitelist Packages that will be able to access the network directly when VPN + * is in lockdown mode but not connected. Has no effect when clearing. + * @throws SecurityException if {@code admin} is not a device or a profile + * owner. + * @throws NameNotFoundException if {@code vpnPackage} or one of + * {@code lockdownWhitelist} is not installed. + * @throws UnsupportedOperationException if {@code vpnPackage} exists but does + * not support being set as always-on, or if always-on VPN is not + * available. + */ + public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) + throws NameNotFoundException, UnsupportedOperationException { throwIfParentInstance("setAlwaysOnVpnPackage"); if (mService != null) { try { - if (!mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled)) { - throw new NameNotFoundException(vpnPackage); + mService.setAlwaysOnVpnPackage( + admin, vpnPackage, lockdownEnabled, lockdownWhitelist); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ERROR_VPN_PACKAGE_NOT_FOUND: + throw new NameNotFoundException(e.getMessage()); + default: + throw new RuntimeException( + "Unknown error setting always-on VPN: " + e.errorCode); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -5089,6 +5140,51 @@ public class DevicePolicyManager { } /** + * Called by device or profile owner to query whether current always-on VPN is configured in + * lockdown mode. Returns {@code false} when no always-on configuration is set. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * + * @throws SecurityException if {@code admin} is not a device or a profile owner. + * + * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean) + */ + public boolean isAlwaysOnVpnLockdownEnabled(@NonNull ComponentName admin) { + throwIfParentInstance("isAlwaysOnVpnLockdownEnabled"); + if (mService != null) { + try { + return mService.isAlwaysOnVpnLockdownEnabled(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device or profile owner to query the list of packages that are allowed to access + * the network directly when always-on VPN is in lockdown mode but not connected. Returns + * {@code null} when always-on VPN is not active or not in lockdown mode. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * + * @throws SecurityException if {@code admin} is not a device or a profile owner. + * + * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List) + */ + public List<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) { + throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist"); + if (mService != null) { + try { + return mService.getAlwaysOnVpnLockdownWhitelist(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** * Called by a device or profile owner to read the name of the package administering an * always-on VPN connection for the current user. If there is no such package, or the always-on * VPN is provided by the system instead of by an application, {@code null} will be returned. @@ -10428,76 +10524,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(); } @@ -10506,22 +10579,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 { @@ -10535,27 +10607,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..5790fda718a7 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -187,8 +187,10 @@ interface IDevicePolicyManager { void setCertInstallerPackage(in ComponentName who, String installerPackage); String getCertInstallerPackage(in ComponentName who); - boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown); + boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownWhitelist); String getAlwaysOnVpnPackage(in ComponentName who); + boolean isAlwaysOnVpnLockdownEnabled(in ComponentName who); + List<String> getAlwaysOnVpnLockdownWhitelist(in ComponentName who); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); @@ -424,8 +426,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/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/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 d2934b9f5a21..b1500c193820 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -55,6 +55,9 @@ 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); diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index d2de8872c1bd..51397a243420 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -619,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. @@ -682,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. @@ -736,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 @@ -743,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. @@ -766,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. diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index d2d0cf9ca90b..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; @@ -270,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/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 97bc0796e9ce..ab8c196edccd 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1900,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 @@ -2051,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) { @@ -2468,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, @@ -2525,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; } @@ -3253,7 +3276,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - public abstract class MetadataListener { + public abstract static class MetadataListener { /** * Callback triggered if the metadata of {@link BluetoothDevice} registered in * {@link #registerMetadataListener}. diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 17cf702bbdea..4d8dc35d7148 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -532,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 @@ -1592,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}. * 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..b8670dbeadad 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -185,7 +185,6 @@ public interface BluetoothProfile { /** * Hearing Aid Device * - * @hide */ int HEARING_AID = 21; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b46203c7a682..edd765b05415 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3047,6 +3047,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 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/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..73b1f4e7e536 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); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9e2f31684395..783ee641d1f7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2223,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. 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/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..28e953593f23 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -24,15 +24,19 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; 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 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 +85,46 @@ 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; + private final ColorDisplayManagerInternal mManager; + private MetricsLogger mMetricsLogger; /** * @hide @@ -91,6 +134,158 @@ 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)); + } + + /** * Returns whether the device has a wide color gamut display. * * @hide @@ -138,6 +333,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 +384,13 @@ public final class ColorDisplayManager { return mManager.getTransformCapabilities(); } + private MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } + private static class ColorDisplayManagerInternal { private static ColorDisplayManagerInternal sInstance; @@ -192,6 +416,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(); diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 53cb8db8cc3d..1918fd5d8cb1 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,16 @@ 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); }
\ No newline at end of file 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/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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c809ccad5907..243b0ebab8f9 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 @@ -1007,14 +1014,20 @@ public class ConnectivityManager { * to remove an existing always-on VPN configuration. * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or * {@code false} otherwise. + * @param lockdownWhitelist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be whitelisted is installed or + * uninstalled. * @return {@code true} if the package is set as always-on VPN controller; * {@code false} otherwise. * @hide */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled) { + boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) { try { - return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled); + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownWhitelist); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1029,6 +1042,7 @@ public class ConnectivityManager { * or {@code null} if none is set. * @hide */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) public String getAlwaysOnVpnPackageForUser(int userId) { try { return mService.getAlwaysOnVpnPackage(userId); @@ -1038,6 +1052,36 @@ public class ConnectivityManager { } /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List<String> getVpnLockdownWhitelist(int userId) { + try { + return mService.getVpnLockdownWhitelist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -1699,6 +1743,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 +1848,8 @@ public class ConnectivityManager { /** * Starts an IPsec NAT-T keepalive packet with the specified parameters. * + * @deprecated Use {@link #createSocketKeepalive} instead. + * * @hide */ @UnsupportedAppUsage @@ -1821,6 +1869,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/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 131925ec28e9..fd7360fd4c17 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -125,8 +125,11 @@ interface IConnectivityManager boolean updateLockdownVpn(); boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List<String> lockdownWhitelist); String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List<String> getVpnLockdownWhitelist(int userId); int checkMobileProvisioning(int suggestedTimeOutMs); @@ -181,6 +184,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/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/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/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/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/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 5bf909566f3b..efcad3ece97d 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -369,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. @@ -425,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."); @@ -466,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; diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 63912ec327a4..630bd2e509ff 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -54,6 +54,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 +394,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 +558,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; } /** 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/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index c167ea18f0c5..8bd75d779154 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -44,6 +44,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 +219,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 +769,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 @@ -1758,9 +1761,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 @@ -1968,10 +1972,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/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 87efbf3b2397..cd823a9c8997 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 @@ -104,6 +105,24 @@ public final class DeviceConfig { 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 @@ -121,6 +140,47 @@ public final class DeviceConfig { 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"; + } + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners = @@ -136,9 +196,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 @@ -160,14 +219,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) @@ -186,9 +244,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) @@ -205,12 +262,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) @@ -242,9 +298,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( @@ -345,8 +400,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..f5c442f194ba 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -89,9 +89,19 @@ 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"; /** @@ -1566,7 +1576,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 +2759,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 diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fa3f54ae0c3f..0961bc37afca 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5800,6 +5800,16 @@ public final class Settings { public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown"; /** + * Comma separated list of packages that are allowed to access the network when VPN is in + * lockdown mode but not running. + * @see #ALWAYS_ON_VPN_LOCKDOWN + * + * @hide + */ + public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST = + "always_on_vpn_lockdown_whitelist"; + + /** * Whether applications can be installed for this user via the system's * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism. * @@ -7802,6 +7812,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 +8249,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 +8444,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 +8560,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 +8589,8 @@ public final class Settings { LOCK_SCREEN_WHEN_TRUST_LOST, SKIP_GESTURE, SILENCE_GESTURE, + THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + AWARE_ENABLED, }; /** @@ -8676,6 +8723,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 +8762,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); } /** @@ -9452,23 +9503,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 +9527,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 */ @@ -12121,6 +12163,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 @@ -12150,6 +12199,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 @@ -12194,6 +12251,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 @@ -12205,7 +12287,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); @@ -12959,48 +13040,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 @@ -13196,6 +13266,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, @@ -13234,6 +13306,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); @@ -13748,6 +13824,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 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/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/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/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/View.java b/core/java/android/view/View.java index 2014ec2417ac..991b385a6cca 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) { @@ -13994,7 +14003,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 +14744,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 +14768,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 +14789,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 +14819,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 +14862,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 +25486,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 +25496,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 +27086,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/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/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 15088d2070d2..0cb1800996c9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2490,7 +2490,14 @@ 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) { @@ -2675,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..fdc34b3f68d0 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); } @@ -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/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..30137e3893ff 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -103,7 +103,9 @@ public class ChooserActivity extends ResolverActivity { * 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 +138,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 +185,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; @@ -630,6 +636,7 @@ public class ChooserActivity extends ResolverActivity { // 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++) { @@ -646,6 +653,13 @@ public class ChooserActivity extends ResolverActivity { 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); } }); } @@ -1178,13 +1192,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..b03fde7a3fad 100644 --- a/core/java/com/android/internal/app/ColorDisplayController.java +++ b/core/java/com/android/internal/app/ColorDisplayController.java @@ -22,7 +22,8 @@ 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.net.Uri; import android.os.Handler; import android.os.Looper; @@ -32,12 +33,9 @@ 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; /** @@ -52,33 +50,7 @@ public final class 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 }) + @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC}) public @interface ColorMode {} /** @@ -108,10 +80,10 @@ public final class ColorDisplayController { 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 +92,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 +109,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 +126,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,8 +193,7 @@ 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); + return mColorDisplayManager.setNightDisplayColorTemperature(colorTemperature); } /** @@ -411,24 +284,14 @@ public final class ColorDisplayController { * 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); + return ColorDisplayManager.getMaximumColorTemperature(mContext); } /** @@ -526,13 +389,6 @@ public final class ColorDisplayController { } } - private MetricsLogger getMetricsLogger() { - if (mMetricsLogger == null) { - mMetricsLogger = new MetricsLogger(); - } - return mMetricsLogger; - } - /** * Callback invoked whenever the Night display settings are changed. */ 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/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/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/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_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_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0453195e6a1d..69877c7d3930 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -360,7 +360,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(); diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 318ec9b2ff0d..40529191a42c 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -1038,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); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 2e7184b4f0fb..8681d4b3f42e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -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..4e486630adae 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 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/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 415857771899..f06165cc7e00 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; @@ -535,6 +539,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 +745,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 e68f9dbbc9b7..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; @@ -273,7 +278,39 @@ message ConstantsProto { } 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 { @@ -807,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/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 96b8dc222e7e..6f7312f79720 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -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 --> <!-- ======================================= --> @@ -3522,6 +3528,12 @@ android:protectionLevel="signature|privileged" /> <uses-permission android:name="android.permission.CONTROL_VPN" /> + <!-- Allows an application to access and modify always-on VPN configuration. + <p>Not for use by third-party or privileged applications. + @hide --> + <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" + android:protectionLevel="signature" /> + <!-- Allows an application to capture audio output. <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" 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..49f2c84335c5 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> @@ -3723,9 +3729,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 +3759,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..d5561302fdc6 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,6 @@ <!-- For CBRS --> <java-symbol type="bool" name="config_cbrs_supported" /> + + <java-symbol type="bool" name="config_awareSettingAvailable" /> </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/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 143174125fe4..9b79e85ae531 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -298,6 +298,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, @@ -388,12 +389,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, @@ -483,10 +481,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, @@ -564,7 +564,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, @@ -572,6 +574,7 @@ public class SettingsBackupTest { Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS, Settings.Secure.ALWAYS_ON_VPN_APP, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, Settings.Secure.ANDROID_ID, Settings.Secure.ANR_SHOW_BACKGROUND, Settings.Secure.ASSISTANT, 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/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/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 7009fb2ea758..4d78e4036e74 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); } 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/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/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/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/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/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 47c90948bbbe..a00a36f93501 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -476,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 e9957df95f10..7381e0417a2d 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -116,7 +116,6 @@ protected: renderthread::RenderThread& mRenderThread; SkColorType mSurfaceColorType; - SkColorSpace::Gamut mSurfaceColorGamut; sk_sp<SkColorSpace> mSurfaceColorSpace; private: 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/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/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index ab59af71d344..720c60362a55 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -241,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) { 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/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/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..88ed9c6a05a9 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,88 @@ 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", + "apex/java/android/media/Session2Token.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..65b6f55a068a 100644 --- a/media/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java diff --git a/media/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 887b4475a4d1..887b4475a4d1 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java 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 fdd07fdd52e3..fdd07fdd52e3 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/apex/java/android/media/MediaSession2.java diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java index 5bb746a7f9e3..5bb746a7f9e3 100644 --- a/media/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java 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/Session2Token.java b/media/apex/java/android/media/Session2Token.java index 238cc2b8ee7d..238cc2b8ee7d 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/apex/java/android/media/Session2Token.java 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 2d59e442abf0..adc14a550b7d 100644 --- a/media/java/android/media/session/ControllerCallbackLink.java +++ b/media/apex/java/android/media/session/ControllerCallbackLink.java @@ -315,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 56ae852d6f50..56ae852d6f50 100644 --- a/media/java/android/media/session/ISessionControllerCallback.aidl +++ b/media/apex/java/android/media/session/ISessionControllerCallback.aidl 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 f159a9538835..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) { 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 0d0ec4c78394..6b28c976c710 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/apex/java/android/media/session/PlaybackState.java @@ -41,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, @@ -191,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 @@ -234,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 @@ -242,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 @@ -252,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 @@ -274,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; @@ -534,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}. @@ -588,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 2fbc6998576a..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, @@ -607,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; } @@ -648,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(); @@ -819,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. @@ -829,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/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/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 4896d0803e42..1a185e982cf1 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -481,7 +481,7 @@ public final class MediaSession { * @hide */ @SystemApi - ControllerLink getControllerLink() { + public ControllerLink getControllerLink() { return mControllerLink; } 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/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..5fae9d5a7974 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -44,6 +44,76 @@ 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)); + + Vector<ui::ColorMode> colorModes; + status_t err = client->getDisplayColorModes(display, &colorModes); + if (err) { + ALOGE("unable to get wide color support"); + return false; + } + + bool wideColorBoardConfig = + getBool<ISurfaceFlingerConfigs, + &ISurfaceFlingerConfigs::hasWideColorDisplay>(false); + + for (android::ui::ColorMode colorMode : colorModes) { + switch (colorMode) { + case ui::ColorMode::DISPLAY_P3: + case ui::ColorMode::ADOBE_RGB: + case ui::ColorMode::DCI_P3: + if (wideColorBoardConfig) { + return true; + } + break; + default: + break; + } + } + return false; +} + +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 +501,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/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/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..bc6e2fc7fc48 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); } 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/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/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/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index e843eb43a3a6..aff6f0452533 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); @@ -832,6 +837,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 +896,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 +1123,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 +1273,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 +1572,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 +2388,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/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8be67d9a7a51..0a62b7c1bde2 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -73,7 +73,7 @@ android_library { "com.android.keyguard", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } android_library { @@ -127,7 +127,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/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/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/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/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 7b6e79be64db..e8432b9a7017 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -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; @@ -251,7 +252,7 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati // 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)); } 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/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/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..7b94c7450637 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,40 @@ public class NotificationLogger implements StateListener { } }; + /** + * Returns the location of the notification referenced by the given {@link NotificationEntry}. + */ + public static NotificationVisibility.NotificationLocation getNotificationLocation( + NotificationEntry entry) { + ExpandableNotificationRow row = entry.getRow(); + ExpandableViewState childViewState = row.getViewState(); + + if (childViewState == null) { + return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN; + } + return convertNotificationLocation(childViewState.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 +399,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 +435,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 +456,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 +501,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 +516,8 @@ public class NotificationLogger implements StateListener { Boolean mIsExpanded; @Nullable Boolean mIsVisible; + @Nullable + NotificationVisibility.NotificationLocation mLocation; private State() {} @@ -484,10 +525,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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 2129b81b1448..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 @@ -174,6 +174,7 @@ 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; @@ -533,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 @@ -548,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() { @@ -2600,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; @@ -2619,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; } } @@ -3255,6 +3266,9 @@ 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)) { @@ -5756,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; @@ -5784,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; } @@ -6315,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; } 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/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/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/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/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/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..bcff4e0a90f1 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); + 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/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 aa74600cc527..1519c1785070 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.shared.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); @@ -1869,6 +1884,12 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceControlAlwaysOnVpnPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, + "ConnectivityService"); + } + private void enforceNetworkStackSettingsOrSetup() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -1876,6 +1897,12 @@ public class ConnectivityService extends IConnectivityManager.Stub android.Manifest.permission.NETWORK_STACK); } + private void enforceNetworkStackPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.NETWORK_STACK, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.NETWORK_STACK); @@ -2476,6 +2503,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 +2528,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 +2560,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 +2574,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"); @@ -3237,9 +3283,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; @@ -3252,10 +3304,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); @@ -3273,7 +3327,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) { @@ -3282,7 +3336,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); } } @@ -3428,6 +3482,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; } } } @@ -3685,20 +3742,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); } } @@ -3722,11 +3805,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(); @@ -4077,8 +4159,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) { - enforceConnectivityInternalPermission(); + public boolean setAlwaysOnVpnPackage( + int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) { + enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { @@ -4092,11 +4175,11 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } - if (!vpn.setAlwaysOnPackage(packageName, lockdown)) { + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) { return false; } if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); return false; } } @@ -4105,7 +4188,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String getAlwaysOnVpnPackage(int userId) { - enforceConnectivityInternalPermission(); + enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { @@ -4119,6 +4202,36 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public boolean isVpnLockdownEnabled(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + Slog.w(TAG, "User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.getLockdown(); + } + } + + @Override + public List<String> getVpnLockdownWhitelist(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + Slog.w(TAG, "User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getLockdownWhitelist(); + } + } + + @Override public int checkMobileProvisioning(int suggestedTimeOutMs) { // TODO: Remove? Any reason to trigger a provisioning check? return -1; @@ -4347,7 +4460,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user " + userId); - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); } } } @@ -5893,12 +6006,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 @@ -5917,10 +6024,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 @@ -6185,6 +6298,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)); @@ -6216,7 +6340,7 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mVpns) { final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false); + setAlwaysOnVpnPackage(userId, null, false, null); setVpnPackageAuthorization(alwaysOnPackage, userId, false); } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 8dcc1d57c572..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); @@ -1299,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; } @@ -1988,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; } @@ -2052,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; } @@ -2163,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 @@ -2306,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; } @@ -2418,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); @@ -3056,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/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/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index dd2b33ab1179..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,13 +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"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -106,13 +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; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -232,23 +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; - // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -295,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) { @@ -309,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) { @@ -347,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, @@ -406,13 +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); + + // For new flags that are intended for server-side experiments, please use the new + // DeviceConfig package. updateMaxCachedProcesses(); } @@ -429,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 @@ -503,8 +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.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 2f77ed64c670..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); } @@ -4882,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 { @@ -7228,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. @@ -9340,6 +9330,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -9739,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) { @@ -14656,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: 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/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/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..250884431440 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -151,7 +151,7 @@ public class Vpn { .divide(BigInteger.valueOf(100)); } // How many routes to evaluate before bailing and declaring this Vpn should provide - // the INTERNET capability. This is necessary because computing the adress space is + // the INTERNET capability. This is necessary because computing the address space is // O(n²) and this is running in the system service, so a limit is needed to alleviate // the risk of attack. // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm @@ -194,6 +194,12 @@ public class Vpn { private boolean mLockdown = false; /** + * Set of packages in addition to the VPN app itself that can access the network directly when + * VPN is not connected even if {@code mLockdown} is set. + */ + private @NonNull List<String> mLockdownWhitelist = Collections.emptyList(); + + /** * List of UIDs for which networking should be blocked until VPN is ready, during brief periods * when VPN is not running. For example, during system startup or after a crash. * @see mLockdown @@ -320,9 +326,9 @@ public class Vpn { * * Used to enable/disable legacy VPN lockdown. * - * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)}; - * previous settings from calling that function will be replaced and saved with the - * always-on state. + * This uses the same ip rule mechanism as + * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling + * that function will be replaced and saved with the always-on state. * * @param lockdown whether to prevent all traffic outside of a VPN. */ @@ -419,12 +425,14 @@ public class Vpn { * * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. + * @param lockdownWhitelist packages to be whitelisted from lockdown. * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ - public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) { + public synchronized boolean setAlwaysOnPackage( + String packageName, boolean lockdown, List<String> lockdownWhitelist) { enforceControlPermissionOrInternalCaller(); - if (setAlwaysOnPackageInternal(packageName, lockdown)) { + if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) { saveAlwaysOnPackage(); return true; } @@ -439,15 +447,27 @@ public class Vpn { * * @param packageName the package to designate as always-on VPN supplier. * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting. + * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if + * {@code lockdown} is {@code true}. Packages must not contain commas. * @return {@code true} if the package has been set as always-on, {@code false} otherwise. */ @GuardedBy("this") - private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) { + private boolean setAlwaysOnPackageInternal( + String packageName, boolean lockdown, List<String> lockdownWhitelist) { if (VpnConfig.LEGACY_VPN.equals(packageName)) { Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on."); return false; } + if (lockdownWhitelist != null) { + for (String pkg : lockdownWhitelist) { + if (pkg.contains(",")) { + Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg); + return false; + } + } + } + if (packageName != null) { // Pre-authorize new always-on VPN package. if (!setPackageAuthorization(packageName, true)) { @@ -460,13 +480,18 @@ public class Vpn { } mLockdown = (mAlwaysOn && lockdown); + mLockdownWhitelist = (mLockdown && lockdownWhitelist != null) + ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist)) + : Collections.emptyList(); + if (isCurrentPreparedPackage(packageName)) { updateAlwaysOnNotification(mNetworkInfo.getDetailedState()); + setVpnForcedLocked(mLockdown); } else { // Prepare this app. The notification will update as a side-effect of updateState(). + // It also calls setVpnForcedLocked(). prepareInternal(packageName); } - setVpnForcedLocked(mLockdown); return true; } @@ -478,7 +503,6 @@ public class Vpn { * @return the package name of the VPN controller responsible for always-on VPN, * or {@code null} if none is set or always-on VPN is controlled through * lockdown instead. - * @hide */ public synchronized String getAlwaysOnPackage() { enforceControlPermissionOrInternalCaller(); @@ -486,6 +510,13 @@ public class Vpn { } /** + * @return an immutable list of packages whitelisted from always-on VPN lockdown. + */ + public synchronized List<String> getLockdownWhitelist() { + return mLockdown ? mLockdownWhitelist : null; + } + + /** * Save the always-on package and lockdown config into Settings.Secure */ @GuardedBy("this") @@ -496,6 +527,9 @@ public class Vpn { getAlwaysOnPackage(), mUserHandle); mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle); + mSystemServices.settingsSecurePutStringForUser( + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, + String.join(",", mLockdownWhitelist), mUserHandle); } finally { Binder.restoreCallingIdentity(token); } @@ -512,7 +546,11 @@ public class Vpn { Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle); final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser( Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0; - setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown); + final String whitelistString = mSystemServices.settingsSecureGetStringForUser( + Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle); + final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString) + ? Collections.emptyList() : Arrays.asList(whitelistString.split(",")); + setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages); } finally { Binder.restoreCallingIdentity(token); } @@ -532,7 +570,7 @@ public class Vpn { } // Remove always-on VPN if it's not supported. if (!isAlwaysOnPackageSupported(alwaysOnPackage)) { - setAlwaysOnPackage(null, false); + setAlwaysOnPackage(null, false, null); return false; } // Skip if the service is already established. This isn't bulletproof: it's not bound @@ -793,6 +831,8 @@ public class Vpn { } } + lp.setHttpProxy(mConfig.proxyInfo); + if (!allowIPv4) { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); } @@ -1247,9 +1287,10 @@ public class Vpn { } /** - * Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN - * service app itself, to only sockets that have had {@code protect()} called on them. All - * non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel. + * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN + * service app itself and whitelisted packages, to only sockets that have had {@code protect()} + * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the + * kernel. * * The exception for the VPN UID isn't technically necessary -- setup should use protected * sockets -- but in practice it saves apps that don't protect their sockets from breaking. @@ -1265,8 +1306,13 @@ public class Vpn { */ @GuardedBy("this") private void setVpnForcedLocked(boolean enforce) { - final List<String> exemptedPackages = - isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage); + final List<String> exemptedPackages; + if (isNullOrLegacyVpn(mPackage)) { + exemptedPackages = null; + } else { + exemptedPackages = new ArrayList<>(mLockdownWhitelist); + exemptedPackages.add(mPackage); + } final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers); Set<UidRange> addedRanges = Collections.emptySet(); diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java index 3a58160cae8d..58c88b36f09d 100644 --- a/services/core/java/com/android/server/display/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/ColorDisplayService.java @@ -16,6 +16,10 @@ 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 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,7 +44,9 @@ 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.IColorDisplayManager; +import android.hardware.display.Time; import android.net.Uri; import android.opengl.Matrix; import android.os.Binder; @@ -103,59 +109,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 @@ -386,7 +350,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]; @@ -445,7 +409,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 +512,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 +595,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 +636,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 +644,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 +671,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 +681,7 @@ public final class ColorDisplayService extends SystemService { mNightDisplayTintController .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode)); - mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature()); + mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting()); updateDisplayWhiteBalanceStatus(); @@ -901,6 +850,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. @@ -941,6 +955,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 +1028,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 +1055,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 +1124,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 +1252,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 +1419,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 +1430,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; } } } @@ -1294,7 +1449,8 @@ public final class ColorDisplayService extends SystemService { 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 boolean isDeviceColorManaged() { @@ -1354,6 +1510,135 @@ 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; 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 bd12075fdad3..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); } } @@ -546,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 = @@ -766,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); @@ -851,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_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, ""); - 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, ""); + 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(); @@ -917,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); @@ -3371,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; @@ -3548,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: "); @@ -3560,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) -> { @@ -3703,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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 47a55971b9c7..a1646862de9f 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); } } 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/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/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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b8342cf8f81e..9100f6aec6d9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -10574,8 +10574,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()); @@ -20672,7 +20670,6 @@ public class PackageManagerService extends IPackageManager.Stub storage.registerListener(mStorageListener); mInstallerService.systemReady(); - mDexManager.systemReady(); mPackageDexOptimizer.systemReady(); getStorageManagerInternal().addExternalStoragePolicy( diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 022c1aad8113..692c032b1f70 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -40,10 +40,10 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; 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; @@ -634,9 +634,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 +2307,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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index aaa187468f8d..8d64b810b407 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -666,6 +666,7 @@ public class UserRestrictionsUtils { case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP: case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN: + case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST: // Whitelist system uid (ConnectivityService) and root uid to change always-on vpn final int appId = UserHandle.getAppId(callingUid); if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) { 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 1a2b11559446..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 @@ -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/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/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 4015c4f7b391..a4f306489c60 100644 --- a/services/core/java/com/android/server/rollback/RollbackData.java +++ b/services/core/java/com/android/server/rollback/RollbackData.java @@ -49,6 +49,14 @@ class RollbackData { */ public Instant timestamp; + /** + * 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 7f515bf63bc1..8b4c410000bb 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -19,8 +19,6 @@ package com.android.server.rollback; 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; @@ -36,11 +34,9 @@ import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; 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; @@ -66,7 +62,6 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.function.Consumer; /** * Implementation of service that manages APK level rollbacks. @@ -116,6 +111,7 @@ 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; @@ -128,6 +124,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { 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 @@ -285,14 +283,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Log.i(TAG, "Initiating rollback of " + targetPackageName); // Get the latest RollbackData for the target package. - RollbackData data = getRollbackForPackage(targetPackageName); + final RollbackData data = getRollbackForPackage(targetPackageName); if (data == null) { sendFailure(statusReceiver, "No rollback available for package."); return; } if (data.rollbackId != rollback.getRollbackId()) { - sendFailure(statusReceiver, "Rollback for package is out of date"); + sendFailure(statusReceiver, "Rollback for package is out of date."); + return; + } + + if (data.inProgress) { + sendFailure(statusReceiver, "Rollback for package is already in progress."); return; } @@ -371,27 +374,36 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { final LocalIntentReceiver receiver = new LocalIntentReceiver( (Intent result) -> { - 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); - - // 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); + 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_PACKAGE_ROLLBACK_EXECUTED); + + // 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); + }); } ); + data.inProgress = true; parentSession.commit(receiver.getIntentSender()); } catch (IOException e) { Log.e(TAG, "Unable to roll back " + targetPackageName, e); @@ -774,10 +786,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - // TODO(narayan): Should we make sure we're in the middle of a session commit for a - // a package with this package name ? Otherwise it's possible we may roll back data - // for some other downgrade. - if (getRollbackForPackage(packageName) == null) { + final RollbackData rollbackData = getRollbackForPackage(packageName); + if (rollbackData == null) { + pmi.finishPackageInstall(token, false); + return; + } + + if (!rollbackData.inProgress) { + Log.e(TAG, "Request to restore userData for: " + packageName + + ", but no rollback in progress."); pmi.finishPackageInstall(token, false); return; } @@ -805,26 +822,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { }); } - private 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) { - getHandler().post(() -> mConsumer.accept(intent)); - } - }; - - public IntentSender getIntentSender() { - return new IntentSender((IIntentSender) mLocalSender); - } - } - /** * Gets the version of the package currently installed. * Returns null if the package is not currently installed. @@ -891,7 +888,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); 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..1f2f1ccd7383 --- /dev/null +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -0,0 +1,94 @@ +/* + * 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.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); + RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName); + if (rollback != null) { + // 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.executeRollback(rollback, rollbackReceiver.getIntentSender()); + } + + @Override + public String getName() { + return NAME; + } +} 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..acede7d4fa90 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -1705,6 +1705,27 @@ 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); + } + } + /** * Pulls various data. */ @@ -1867,6 +1888,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.TEMPERATURE: { + pullTemperature(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/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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d36e545aa74f..3a077b86b89a 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; } @@ -892,8 +901,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 +915,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 +925,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 +960,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 +973,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 2affa974e999..e5a66cb4238f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5632,6 +5632,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 @@ -6222,30 +6227,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 @@ -7044,5 +7046,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..f3a363a30cf8 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -174,6 +174,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 +319,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 +415,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 +423,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 +499,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; } @@ -2128,6 +2126,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 +2226,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..49308b8f92b4 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; @@ -89,8 +89,9 @@ public class AppTransitionController { * 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 +109,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 +146,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 +157,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 +174,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 +193,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 +202,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); @@ -237,29 +248,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 +279,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 +379,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 +416,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 +440,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..29645f68b2fb 100644 --- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java @@ -50,9 +50,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(); @@ -86,6 +100,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 +118,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 +171,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..65b36a092228 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,6 +99,7 @@ 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; @@ -117,6 +120,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 +265,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 +287,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 +826,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 +1545,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,9 +1577,68 @@ 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); + + 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); + } + @Override void checkAppWindowsReadyToShow() { if (allDrawn == mLastAllDrawn) { @@ -2242,6 +2319,15 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return getBounds(); } + private static boolean isChangeTransition(int transit) { + return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE; + } + + private int getDefaultChangeTransitionDuration() { + return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION + * mWmService.getTransitionAnimationScaleLocked()); + } + boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { @@ -2260,13 +2346,35 @@ 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) && mThumbnail != null; + // 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) { + int duration = getDefaultChangeTransitionDuration(); + mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); + adapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), duration, + true /* isAppAnimation */, false /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + thumbnailAdapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), duration, + true /* isAppAnimation */, true /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + mTransit = transit; + mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); } else { final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode(); @@ -2294,6 +2402,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 +2541,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 +2610,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 +2647,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 +2980,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8fefd352e027..8f976e74670d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -250,6 +250,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; @@ -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/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..f760b39c5332 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,84 @@ 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); + 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 +392,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 +403,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 +432,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 +444,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/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..7dd7c4f5c958 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java @@ -0,0 +1,203 @@ +/* + * 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; + + public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo, + long duration, boolean isAppAnimation, boolean isThumbnail) { + mStartBounds = new Rect(startBounds); + mEndBounds = new Rect(endBounds); + mIsAppAnimation = isAppAnimation; + mIsThumbnail = isThumbnail; + createBoundsInterpolator(duration, 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..5eff7d8a8553 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() { @@ -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/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 54053a896df9..90888201b89a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -70,6 +70,7 @@ 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; @@ -77,14 +78,10 @@ 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; -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.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.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -949,8 +946,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 +1069,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 +1342,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 +1542,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 +1760,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); + } } } @@ -1916,7 +1919,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } AlarmManager getAlarmManager() { - return (AlarmManager) mContext.getSystemService(AlarmManager.class); + return mContext.getSystemService(AlarmManager.class); + } + + ConnectivityManager getConnectivityManager() { + return mContext.getSystemService(ConnectivityManager.class); } IWindowManager getIWindowManager() { @@ -6299,7 +6306,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @throws UnsupportedOperationException if the package does not support being set as always-on. */ @Override - public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown) + public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown, + List<String> lockdownWhitelist) throws SecurityException { enforceProfileOrDeviceOwner(admin); @@ -6307,11 +6315,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long token = mInjector.binderClearCallingIdentity(); try { if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) { - return false; + Slog.w(LOG_TAG, "Non-existent VPN package specified: " + vpnPackage); + throw new ServiceSpecificException( + DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, vpnPackage); } - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) { + + if (vpnPackage != null && lockdown && lockdownWhitelist != null) { + for (String packageName : lockdownWhitelist) { + if (!isPackageInstalledForUser(packageName, userId)) { + Slog.w(LOG_TAG, "Non-existent package in VPN whitelist: " + packageName); + throw new ServiceSpecificException( + DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, packageName); + } + } + } + // If some package is uninstalled after the check above, it will be ignored by CM. + if (!mInjector.getConnectivityManager().setAlwaysOnVpnPackageForUser( + userId, vpnPackage, lockdown, lockdownWhitelist)) { throw new UnsupportedOperationException(); } DevicePolicyEventLogger @@ -6328,16 +6348,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public String getAlwaysOnVpnPackage(ComponentName admin) + public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException { + enforceProfileOrDeviceOwner(admin); + + final int userId = mInjector.userHandleGetCallingUserId(); + final long token = mInjector.binderClearCallingIdentity(); + try { + return mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId); + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + + @Override + public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException { + enforceProfileOrDeviceOwner(admin); + + final int userId = mInjector.userHandleGetCallingUserId(); + final long token = mInjector.binderClearCallingIdentity(); + try { + return mInjector.getConnectivityManager().isVpnLockdownEnabled(userId); + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + + @Override + public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin) throws SecurityException { enforceProfileOrDeviceOwner(admin); final int userId = mInjector.userHandleGetCallingUserId(); final long token = mInjector.binderClearCallingIdentity(); - try{ - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - return connectivityManager.getAlwaysOnVpnPackageForUser(userId); + try { + return mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId); } finally { mInjector.binderRestoreCallingIdentity(token); } @@ -6362,7 +6406,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(); @@ -6373,7 +6417,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) { @@ -6388,6 +6432,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } + Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty"); enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId()); final ActiveAdmin admin; @@ -6447,7 +6492,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); @@ -6809,9 +6854,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { enforceDeviceOwner(who); long token = mInjector.binderClearCallingIdentity(); try { - ConnectivityManager connectivityManager = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - connectivityManager.setGlobalProxy(proxyInfo); + mInjector.getConnectivityManager().setGlobalProxy(proxyInfo); } finally { mInjector.binderRestoreCallingIdentity(token); } @@ -13988,55 +14031,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(); @@ -14046,7 +14061,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; } } @@ -14062,6 +14077,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); } } @@ -14077,7 +14095,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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 623990ba211a..586136802619 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -873,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(); @@ -1561,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 @@ -2011,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/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/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/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/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 0813e6fa0252..535198b3dbfa 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -5239,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..ca39a7f98f79 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 */); @@ -981,7 +992,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 +1004,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 +1017,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 +1047,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,8 +1092,8 @@ 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); } 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/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/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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c0f9b80b2e08..62229235a026 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++; } 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/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/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 413b6f4b3905..9478be90b5c3 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; @@ -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/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/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 6ad698b39763..85939d498755 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -855,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; @@ -1346,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); } @@ -1447,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 @@ -1652,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/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/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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a33b44c454b4..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. @@ -2575,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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 1378bb004696..d777bf123b67 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,6 +22,10 @@ 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 +36,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 +66,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 +346,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 +395,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 +1006,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. 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/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 9414abd98b1c..5b2e635b179f 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -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; /** diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index d37198a3e25d..086a76546b2d 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 { @@ -180,10 +239,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 +259,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 +280,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 +302,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 +320,55 @@ 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. + */ + @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. + */ + @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/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 9cc173cfcdd6..8237d39c4c3d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -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/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/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 9d67cea05fc8..13ac4f09dd86 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -57,6 +58,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. @@ -663,4 +665,63 @@ public class RollbackTest { 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 = rm.getAvailableRollback(TEST_APP_A); + assertNotNull(rollbackA); + assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName()); + + RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); + assertNotNull(rollbackB); + assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName()); + + // 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); + + // 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(); + } + } } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index fbc3d8f1cd34..edb13556b8fc 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -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/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index dda44819e664..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; } @@ -3548,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) { @@ -3695,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); @@ -4914,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/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 0b74d878f069..5b17224e41e5 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -246,17 +246,17 @@ public class VpnTest { assertFalse(vpn.getLockdown()); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); assertTrue(vpn.getAlwaysOn()); assertTrue(vpn.getLockdown()); // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false)); + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); assertFalse(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); } @@ -270,11 +270,11 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -283,7 +283,7 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[1]); // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -297,6 +297,87 @@ public class VpnTest { } @Test + public void testLockdownWhitelist() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final UidRange user = UidRange.createForUser(primaryUser.id); + + // Set always-on with lockdown and whitelist app PKGS[2] from lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); + + // Change whitelisted app to PKGS[3]. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1), + new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]); + assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]); + + // Change the VPN app. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start, user.start + PKG_UIDS[0] - 1), + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1) + })); + assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); + assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); + + // Remove the whitelist. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), + new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.stop), + })); + assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], + user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[0]); + + // Add the whitelist. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1]))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) + })); + assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); + assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]); + + // Try whitelisting a package with a comma, should be rejected. + assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d"))); + + // Pass a non-existent packages in the whitelist, they (and only they) should be ignored. + // Whitelisted package should change from PGKS[1] to PKGS[2]. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, + Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); + verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{ + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), + new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) + })); + verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{ + new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1), + new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) + })); + } + + @Test public void testLockdownAddingAProfile() throws Exception { final Vpn vpn = createVpn(primaryUser.id); setMockedUsers(primaryUser); @@ -310,7 +391,7 @@ public class VpnTest { final UidRange profile = UidRange.createForUser(tempProfile.id); // Set lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[3] - 1), new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) @@ -436,7 +517,7 @@ public class VpnTest { .cancelAsUser(anyString(), anyInt(), eq(userHandle)); // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false); + vpn.setAlwaysOnPackage(PKGS[0], false, null); order.verify(mNotificationManager) .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); @@ -450,7 +531,7 @@ public class VpnTest { .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false); + vpn.setAlwaysOnPackage(null, false, null); order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); } @@ -583,7 +664,9 @@ public class VpnTest { doAnswer(invocation -> { final String appName = (String) invocation.getArguments()[0]; final int userId = (int) invocation.getArguments()[1]; - return UserHandle.getUid(userId, packages.get(appName)); + Integer appId = packages.get(appName); + if (appId == null) throw new PackageManager.NameNotFoundException(appName); + return UserHandle.getUid(userId, appId); }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); } catch (Exception e) { } diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java index a6b0102a511f..163b00abafcd 100644 --- a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -190,21 +190,22 @@ public class SelectTestTests { @Test public void testSelectClassAndSamePackage() { - final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A, - CLASS_C5, CLASS_C6, PACKAGE_C).build(); - acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + 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_A1L, METHOD_A2M, CLASS_A1, - CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build(); + 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, + 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); } diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp index 2e4315086105..8da5bb8d5dd6 100644 --- a/tools/aapt2/cmd/Convert_test.cpp +++ b/tools/aapt2/cmd/Convert_test.cpp @@ -53,7 +53,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 +91,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); 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/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/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/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1fd45e72f1e8..40077e111654 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -4777,4 +4777,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/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/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); + } +} |