diff options
329 files changed, 13480 insertions, 5150 deletions
diff --git a/Android.bp b/Android.bp index a5cc89cd0ea1..afdcaddf626f 100644 --- a/Android.bp +++ b/Android.bp @@ -271,6 +271,7 @@ java_defaults { "core/java/android/os/IThermalService.aidl", "core/java/android/os/IUpdateLock.aidl", "core/java/android/os/IUserManager.aidl", + ":libvibrator_aidl", "core/java/android/os/IVibratorService.aidl", "core/java/android/os/storage/IStorageManager.aidl", "core/java/android/os/storage/IStorageEventListener.aidl", @@ -561,6 +562,7 @@ java_defaults { "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl", "telephony/java/android/telephony/ICellInfoCallback.aidl", + "telephony/java/android/telephony/IFinancialSmsCallback.aidl", "telephony/java/android/telephony/INetworkService.aidl", "telephony/java/android/telephony/INetworkServiceCallback.aidl", "telephony/java/com/android/ims/internal/IImsCallSession.aidl", @@ -726,7 +728,7 @@ java_defaults { "ext", ], - jarjar_rules: ":framework-hidl-jarjar", + jarjar_rules: "jarjar_rules_hidl.txt", static_libs: [ "apex_aidl_interface-java", @@ -738,6 +740,15 @@ java_defaults { "android.hardware.cas-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.0-java", + "android.hardware.radio-V1.1-java", + "android.hardware.radio-V1.2-java", + "android.hardware.radio-V1.3-java", + "android.hardware.radio-V1.4-java", + "android.hardware.radio.config-V1.0-java", + "android.hardware.radio.config-V1.1-java", + "android.hardware.radio.config-V1.2-java", + "android.hardware.radio.deprecated-V1.0-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.thermal-V1.0-java", "android.hardware.thermal-V1.1-java", @@ -746,15 +757,12 @@ java_defaults { "android.hardware.usb-V1.0-java-constants", "android.hardware.usb-V1.1-java-constants", "android.hardware.usb-V1.2-java-constants", + "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", "android.hardware.wifi-V1.0-java-constants", - "android.hardware.radio-V1.0-java", - "android.hardware.radio-V1.3-java", - "android.hardware.radio-V1.4-java", - "android.hardware.usb.gadget-V1.0-java", "networkstack-aidl-interfaces-java", "netd_aidl_interface-java", "devicepolicyprotosnano", @@ -789,8 +797,11 @@ filegroup { } filegroup { - name: "framework-hidl-jarjar", - srcs: ["jarjar_rules_hidl.txt"], + name: "libvibrator_aidl", + srcs: [ + "core/java/android/os/IExternalVibrationController.aidl", + "core/java/android/os/IExternalVibratorService.aidl", + ], } java_library { @@ -817,19 +828,6 @@ java_library_host { } // A temporary build target that is conditionally included on the bootclasspath if -// org.apache.http.legacy library has been removed and which provides support for -// maintaining backwards compatibility for APKs that target pre-P and depend on -// org.apache.http.legacy classes. This is used iff REMOVE_OAHL_FROM_BCP=true is -// specified on the build command line. -java_library { - name: "framework-oahl-backward-compatibility", - installable: true, - srcs: [ - "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", - ], -} - -// A temporary build target that is conditionally included on the bootclasspath if // android.test.base library has been removed and which provides support for // maintaining backwards compatibility for APKs that target pre-P and depend on // android.test.base classes. This is used iff REMOVE_ATB_FROM_BCP=true is @@ -910,6 +908,31 @@ aidl_interface { api_dir: "aidl/networkstack", } +filegroup { + name: "framework-networkstack-shared-srcs", + srcs: [ + // TODO: remove these annotations as soon as we can use andoid.support.annotations.* + "core/java/android/annotation/NonNull.java", + "core/java/android/annotation/Nullable.java", + "core/java/android/annotation/IntDef.java", + "core/java/android/annotation/IntRange.java", + "core/java/android/annotation/UnsupportedAppUsage.java", + "core/java/android/net/DhcpResults.java", + "core/java/android/util/LocalLog.java", + "core/java/com/android/internal/annotations/VisibleForTesting.java", + "core/java/com/android/internal/util/HexDump.java", + "core/java/com/android/internal/util/IndentingPrintWriter.java", + "core/java/com/android/internal/util/IState.java", + "core/java/com/android/internal/util/MessageUtils.java", + "core/java/com/android/internal/util/Preconditions.java", + "core/java/com/android/internal/util/RingBufferIndices.java", + "core/java/com/android/internal/util/State.java", + "core/java/com/android/internal/util/StateMachine.java", + "core/java/com/android/internal/util/WakeupMessage.java", + "core/java/android/net/shared/*.java", + ] +} + // Build ext.jar // ============================================================ java_library { diff --git a/api/current.txt b/api/current.txt index bb6dbeba2e7a..3d77ffbc450d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3802,6 +3802,7 @@ package android.app { method @Deprecated public void onStateNotSaved(); method @CallSuper protected void onStop(); method protected void onTitleChanged(CharSequence, int); + method public void onTopResumedActivityChanged(boolean); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public void onTrimMemory(int); @@ -5101,7 +5102,7 @@ package android.app { } public class KeyguardManager { - method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); + method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method @Deprecated public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -12512,6 +12513,7 @@ package android.database { method public int getInt(int); method public long getLong(int); method public android.net.Uri getNotificationUri(); + method public default java.util.List<android.net.Uri> getNotificationUris(); method public int getPosition(); method public short getShort(int); method public String getString(int); @@ -12535,6 +12537,7 @@ package android.database { method public android.os.Bundle respond(android.os.Bundle); method public void setExtras(android.os.Bundle); method public void setNotificationUri(android.content.ContentResolver, android.net.Uri); + method public default void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>); method public void unregisterContentObserver(android.database.ContentObserver); method public void unregisterDataSetObserver(android.database.DataSetObserver); field public static final int FIELD_TYPE_BLOB = 4; // 0x4 @@ -13947,8 +13950,6 @@ package android.graphics { @AnyThread public abstract class ColorSpace { method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]); method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation); - method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int); - method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent); method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace); @@ -14167,6 +14168,7 @@ package android.graphics { ctor public ImageFormat(); method public static int getBitsPerPixel(int); field public static final int DEPTH16 = 1144402265; // 0x44363159 + field public static final int DEPTH_JPEG = 1768253795; // 0x69656963 field public static final int DEPTH_POINT_CLOUD = 257; // 0x101 field public static final int FLEX_RGBA_8888 = 42; // 0x2a field public static final int FLEX_RGB_888 = 41; // 0x29 @@ -16171,6 +16173,7 @@ package android.hardware { method public long getUsage(); method public int getWidth(); method public boolean isClosed(); + method public static boolean isSupported(int, int, int, int, long); method public void writeToParcel(android.os.Parcel, int); field public static final int BLOB = 33; // 0x21 field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR; @@ -16498,6 +16501,7 @@ package android.hardware.biometrics { ctor public BiometricPrompt.Builder(android.content.Context); method public android.hardware.biometrics.BiometricPrompt build(); method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); + method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); @@ -23522,6 +23526,8 @@ package android.media { method public int setBufferSizeInFrames(int); method public int setLoopPoints(int, int, int); method public int setNotificationMarkerPosition(int); + method public void setOffloadDelayPadding(int, int); + method public void setOffloadEndOfStream(); method public int setPlaybackHeadPosition(int); method public void setPlaybackParams(@NonNull android.media.PlaybackParams); method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener); @@ -27678,6 +27684,7 @@ package android.media.tv { field public static final String TYPE_DVB_T2 = "TYPE_DVB_T2"; field public static final String TYPE_ISDB_C = "TYPE_ISDB_C"; field public static final String TYPE_ISDB_S = "TYPE_ISDB_S"; + field public static final String TYPE_ISDB_S3 = "TYPE_ISDB_S3"; field public static final String TYPE_ISDB_T = "TYPE_ISDB_T"; field public static final String TYPE_ISDB_TB = "TYPE_ISDB_TB"; field public static final String TYPE_NTSC = "TYPE_NTSC"; @@ -34405,8 +34412,12 @@ package android.os { } public abstract class FileObserver { - ctor public FileObserver(String); - ctor public FileObserver(String, int); + ctor @Deprecated public FileObserver(String); + ctor public FileObserver(@NonNull java.io.File); + ctor public FileObserver(@NonNull java.util.List<java.io.File>); + ctor @Deprecated public FileObserver(String, int); + ctor public FileObserver(@NonNull java.io.File, int); + ctor public FileObserver(@NonNull java.util.List<java.io.File>, int); method protected void finalize(); method public abstract void onEvent(int, @Nullable String); method public void startWatching(); @@ -38642,6 +38653,7 @@ package android.provider { field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS"; field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS"; + field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE = "android.settings.PROCESS_WIFI_EASY_CONNECT_QR_CODE"; field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS"; field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE"; @@ -38675,6 +38687,7 @@ package android.provider { field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; + field public static final String EXTRA_QR_CODE = "android.provider.extra.QR_CODE"; field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID"; field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG"; field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON"; @@ -41405,9 +41418,9 @@ package android.service.notification { 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_CONTEXTUAL_ACTIONS = "key_contextual_actions"; 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_TEXT_REPLIES = "key_text_replies"; field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; } @@ -44662,12 +44675,14 @@ package android.telephony { public final class SmsManager { method public String createAppSpecificSmsToken(android.app.PendingIntent); + method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent); method public java.util.ArrayList<java.lang.String> divideMessage(String); method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent); method public android.os.Bundle getCarrierConfigValues(); method public static android.telephony.SmsManager getDefault(); method public static int getDefaultSmsSubscriptionId(); method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int); + method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback); method public int getSubscriptionId(); method public void injectSmsPdu(byte[], String, android.app.PendingIntent); method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent); @@ -44730,6 +44745,11 @@ package android.telephony { field public static final int STATUS_ON_ICC_UNSENT = 7; // 0x7 } + public abstract static class SmsManager.FinancialSmsCallback { + ctor public SmsManager.FinancialSmsCallback(); + method public abstract void onFinancialSmsMessages(android.database.CursorWindow); + } + public class SmsMessage { method public static int[] calculateLength(CharSequence, boolean); method public static int[] calculateLength(String, boolean); @@ -44999,6 +45019,7 @@ package android.telephony { method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>); field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL"; + field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; @@ -45037,6 +45058,7 @@ package android.telephony { field public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH"; field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; + field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY"; field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; field public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; @@ -48568,6 +48590,7 @@ package android.view { method public String getName(); method @Deprecated public int getOrientation(); method @Deprecated public int getPixelFormat(); + method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace(); method public long getPresentationDeadlineNanos(); method public void getRealMetrics(android.util.DisplayMetrics); method public void getRealSize(android.graphics.Point); @@ -55931,10 +55954,10 @@ package android.widget { method @NonNull public android.widget.Magnifier.Builder setCornerRadius(@Px @FloatRange(from=0) float); method @NonNull public android.widget.Magnifier.Builder setDefaultSourceToMagnifierOffset(@Px int, @Px int); method @NonNull public android.widget.Magnifier.Builder setElevation(@Px @FloatRange(from=0) float); + method @NonNull public android.widget.Magnifier.Builder setInitialZoom(@FloatRange(from=0.0f) float); method @NonNull public android.widget.Magnifier.Builder setOverlay(@Nullable android.graphics.drawable.Drawable); method @NonNull public android.widget.Magnifier.Builder setSize(@Px @IntRange(from=0) int, @Px @IntRange(from=0) int); method @NonNull public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int); - method @NonNull public android.widget.Magnifier.Builder setZoom(@FloatRange(from=0.0f) float); } public class MediaController extends android.widget.FrameLayout { diff --git a/api/system-current.txt b/api/system-current.txt index 9e3e800f8332..e0af359c999b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -508,11 +508,13 @@ package android.app { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long); + method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException; field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; @@ -1265,6 +1267,7 @@ package android.content { field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; + field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; @@ -1303,13 +1306,13 @@ package android.content { field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS"; field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES"; field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION"; - field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED"; field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE"; field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS"; field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; + field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED"; field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; @@ -1695,18 +1698,19 @@ package android.content.rollback { public final class RollbackInfo implements android.os.Parcelable { method public int describeContents(); + method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages(); method public int getRollbackId(); + method public int getSessionId(); + method public boolean isStaged(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR; - field public final android.content.rollback.PackageRollbackInfo targetPackage; } public final class RollbackManager { - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull android.content.IntentSender); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks(); - method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks(); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData(); } @@ -4101,25 +4105,10 @@ package android.net { method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; } - public final class IpSecTransform implements java.lang.AutoCloseable { - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void startNattKeepalive(@NonNull android.net.IpSecTransform.NattKeepaliveCallback, int, @NonNull android.os.Handler) throws java.io.IOException; - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void stopNattKeepalive(); - } - public static class IpSecTransform.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; } - public static class IpSecTransform.NattKeepaliveCallback { - ctor public IpSecTransform.NattKeepaliveCallback(); - method public void onError(int); - method public void onStarted(); - method public void onStopped(); - field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3 - field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2 - field public static final int ERROR_INVALID_NETWORK = 1; // 0x1 - } - public class LinkAddress implements android.os.Parcelable { ctor public LinkAddress(java.net.InetAddress, int, int, int); ctor public LinkAddress(java.net.InetAddress, int); @@ -4249,6 +4238,24 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(android.net.StaticIpConfiguration); + method public void addDnsServer(java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method public java.util.List<java.net.InetAddress> getDnsServers(); + method public String getDomains(); + method public java.net.InetAddress getGateway(); + method public android.net.LinkAddress getIpAddress(); + method public java.util.List<android.net.RouteInfo> getRoutes(String); + method public void setDomains(String); + method public void setGateway(java.net.InetAddress); + method public void setIpAddress(android.net.LinkAddress); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); @@ -4274,6 +4281,47 @@ package android.net { } +package android.net.apf { + + public class ApfCapabilities { + ctor public ApfCapabilities(int, int, int); + method public boolean hasDataAccess(); + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.captiveportal { + + public final class CaptivePortalProbeResult { + ctor public CaptivePortalProbeResult(int); + ctor public CaptivePortalProbeResult(int, String, String); + ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); + method public boolean isFailed(); + method public boolean isPortal(); + method public boolean isSuccessful(); + field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; + field public static final int FAILED_CODE = 599; // 0x257 + field public static final int PORTAL_CODE = 302; // 0x12e + field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; + field public static final int SUCCESS_CODE = 204; // 0xcc + field public final String detectUrl; + field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec; + field public final String redirectUrl; + } + + public abstract class CaptivePortalProbeSpec { + method public String getEncodedSpec(); + method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String); + method public java.net.URL getUrl(); + method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String); + method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String); + } + +} + package android.net.metrics { public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { @@ -5042,6 +5090,7 @@ package android.nfc { package android.os { public class BatteryManager { + method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int); field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS"; field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; } @@ -5059,6 +5108,36 @@ package android.os { method public Object onTransactStarted(android.os.IBinder, int); } + public class BugreportManager { + method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport(); + method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); + } + + public abstract static class BugreportManager.BugreportCallback { + ctor public BugreportManager.BugreportCallback(); + method public void onError(int); + method public void onFinished(); + method public void onProgress(float); + field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 + field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 + field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 + field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 + } + + public final class BugreportParams { + ctor public BugreportParams(@android.os.BugreportParams.BugreportMode int); + method public int getMode(); + field public static final int BUGREPORT_MODE_FULL = 0; // 0x0 + field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1 + field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2 + field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4 + field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3 + field public static final int BUGREPORT_MODE_WIFI = 5; // 0x5 + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix={"BUGREPORT_MODE_"}, value={android.os.BugreportParams.BUGREPORT_MODE_FULL, android.os.BugreportParams.BUGREPORT_MODE_INTERACTIVE, android.os.BugreportParams.BUGREPORT_MODE_REMOTE, android.os.BugreportParams.BUGREPORT_MODE_WEAR, android.os.BugreportParams.BUGREPORT_MODE_TELEPHONY, android.os.BugreportParams.BUGREPORT_MODE_WIFI}) public static @interface BugreportParams.BugreportMode { + } + public static class Build.VERSION { field public static final String PREVIEW_SDK_FINGERPRINT; } @@ -5533,7 +5612,6 @@ package android.permissionpresenterservice { method @Deprecated public final void attachBaseContext(android.content.Context); method @Deprecated public final android.os.IBinder onBind(android.content.Intent); method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String); - method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String); field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService"; } @@ -5665,6 +5743,12 @@ package android.provider { field public static final String NAMESPACE = "activity_manager"; } + public static interface DeviceConfig.AttentionManagerService { + field public static final String NAMESPACE = "attention_manager_service"; + field public static final String PROPERTY_COMPONENT_NAME = "component_name"; + field public static final String PROPERTY_SERVICE_ENABLED = "service_enabled"; + } + public static interface DeviceConfig.FsiBoot { field public static final String NAMESPACE = "fsi_boot"; field public static final String OOB_ENABLED = "oob_enabled"; @@ -5673,8 +5757,8 @@ package android.provider { 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"; + field public static final String PROPERTY_ATTENTION_ENABLED = "attention_enabled"; + field public static final String PROPERTY_ATTENTION_SETTINGS = "attention_settings"; } public static interface DeviceConfig.OnPropertyChangedListener { @@ -7849,7 +7933,7 @@ package android.telephony { package android.telephony.data { public final class DataCallResponse implements android.os.Parcelable { - ctor public DataCallResponse(int, int, int, int, @Nullable String, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int); + ctor public DataCallResponse(int, int, int, int, int, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int); ctor public DataCallResponse(android.os.Parcel); method public int describeContents(); method public int getActive(); @@ -7860,9 +7944,9 @@ package android.telephony.data { method @NonNull public String getIfname(); method public int getMtu(); method @NonNull public java.util.List<java.lang.String> getPcscfs(); + method public int getProtocolType(); method public int getStatus(); method public int getSuggestedRetryTime(); - method @NonNull public String getType(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR; } @@ -7876,8 +7960,8 @@ package android.telephony.data { method public int getMtu(); method public String getPassword(); method public int getProfileId(); - method public String getProtocol(); - method public String getRoamingProtocol(); + method public int getProtocol(); + method public int getRoamingProtocol(); method public int getSupportedApnTypesBitmap(); method public int getType(); method public String getUserName(); @@ -8244,6 +8328,16 @@ package android.telephony.ims { field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants; } + public class ImsException extends java.lang.Exception { + ctor public ImsException(@Nullable String); + ctor public ImsException(@Nullable String, int); + ctor public ImsException(@Nullable String, int, Throwable); + method public int getCode(); + field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1 + field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2 + } + public final class ImsExternalCallState implements android.os.Parcelable { ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean); method public int describeContents(); @@ -8261,7 +8355,7 @@ package android.telephony.ims { } public class ImsMmTelManager { - method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled(); @@ -8270,8 +8364,8 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int); @@ -8706,13 +8800,13 @@ package android.telephony.ims { } public class ProvisioningManager { - method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(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 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.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); + method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b diff --git a/api/test-current.txt b/api/test-current.txt index 0f2ba12bd9ad..049e0025a59b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6,6 +6,7 @@ package android { field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; + field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; @@ -866,6 +867,24 @@ package android.net { field public static final int RTN_UNREACHABLE = 7; // 0x7 } + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(android.net.StaticIpConfiguration); + method public void addDnsServer(java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method public java.util.List<java.net.InetAddress> getDnsServers(); + method public String getDomains(); + method public java.net.InetAddress getGateway(); + method public android.net.LinkAddress getIpAddress(); + method public java.util.List<android.net.RouteInfo> getRoutes(String); + method public void setDomains(String); + method public void setGateway(java.net.InetAddress); + method public void setIpAddress(android.net.LinkAddress); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); @@ -875,6 +894,47 @@ package android.net { } +package android.net.apf { + + public class ApfCapabilities { + ctor public ApfCapabilities(int, int, int); + method public boolean hasDataAccess(); + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.captiveportal { + + public final class CaptivePortalProbeResult { + ctor public CaptivePortalProbeResult(int); + ctor public CaptivePortalProbeResult(int, String, String); + ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec); + method public boolean isFailed(); + method public boolean isPortal(); + method public boolean isSuccessful(); + field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED; + field public static final int FAILED_CODE = 599; // 0x257 + field public static final int PORTAL_CODE = 302; // 0x12e + field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS; + field public static final int SUCCESS_CODE = 204; // 0xcc + field public final String detectUrl; + field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec; + field public final String redirectUrl; + } + + public abstract class CaptivePortalProbeSpec { + method public String getEncodedSpec(); + method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String); + method public java.net.URL getUrl(); + method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String); + method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String); + } + +} + package android.net.metrics { public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { @@ -1053,6 +1113,10 @@ package android.os { method public static java.io.File getStorageDirectory(); } + public class FileUtils { + method public static boolean contains(java.io.File, java.io.File); + } + public abstract class HwBinder implements android.os.IHwBinder { ctor public HwBinder(); method public static final void configureRpcThreadpool(long, boolean); @@ -1218,6 +1282,10 @@ package android.os { method public boolean hasSingleFileDescriptor(); } + public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { + method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException; + } + public final class PowerManager { method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode(); method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int); @@ -1485,17 +1553,37 @@ package android.print { package android.provider { + public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns { + field public static final String[] SYNC_WRITABLE_COLUMNS; + } + + public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns { + field public static final String[] SYNC_WRITABLE_COLUMNS; + } + + public final class ContactsContract { + field public static final String HIDDEN_COLUMN_PREFIX = "x_"; + } + public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { field public static final android.net.Uri ENTERPRISE_CONTENT_URI; } + public static final class ContactsContract.PinnedPositions { + field public static final String UNDEMOTE_METHOD = "undemote"; + } + public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns { field public static final android.net.Uri CORP_CONTENT_URI; } public final class MediaStore { - method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; - method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; + method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException; + method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException; + field public static final String SCAN_FILE_CALL = "scan_file"; + field public static final String SCAN_VOLUME_CALL = "scan_volume"; } public final class Settings { @@ -1555,6 +1643,10 @@ package android.provider { field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION"; } + public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns { + field public static final String _DATA = "_data"; + } + } package android.security { @@ -1829,10 +1921,15 @@ package android.telephony { } public class TelephonyManager { + method public int checkCarrierPrivilegesForPackage(String); method public int getCarrierIdListVersion(); method public boolean isRttSupported(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile(); method public void setCarrierTestOverride(String, String, String, String, String, String, String); + field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe + field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 + field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 + field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff } diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 13157505fc28..deebd4e3cd3b 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,6 +1,7 @@ bookatz@google.com cjyu@google.com dwchen@google.com +gaillard@google.com jinyithu@google.com joeo@google.com kwekua@google.com diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 9c320d3e2b03..b26c713877db 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -989,6 +989,25 @@ Status StatsService::setDataFetchOperation(int64_t key, return Status::ok(); } +Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, + const String16& packageName, + vector<int64_t>* output) { + ENFORCE_DUMP_AND_USAGE_STATS(packageName); + + IPCThreadState* ipc = IPCThreadState::self(); + mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender); + //TODO: Return the list of configs that are already active + return Status::ok(); +} + +Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) { + ENFORCE_DUMP_AND_USAGE_STATS(packageName); + + IPCThreadState* ipc = IPCThreadState::self(); + mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid()); + return Status::ok(); +} + Status StatsService::removeConfiguration(int64_t key, const String16& packageName) { ENFORCE_DUMP_AND_USAGE_STATS(packageName); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index fe0504fc034f..cdff50fcb62e 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -132,6 +132,17 @@ public: const String16& packageName) override; /** + * Binder call to let clients register the active configs changed operation. + */ + virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, + const String16& packageName, + vector<int64_t>* output) override; + + /** + * Binder call to remove the active configs changed operation for the specified package.. + */ + virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override; + /** * Binder call to allow clients to remove the specified configuration. */ virtual Status removeConfiguration(int64_t key, diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index d6b4737f5bbd..e84eced8bb1a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -28,10 +28,12 @@ import "frameworks/base/core/proto/android/bluetooth/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto"; import "frameworks/base/core/proto/android/debug/enums.proto"; +import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto"; import "frameworks/base/core/proto/android/net/networkcapabilities.proto"; import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto"; import "frameworks/base/core/proto/android/server/enums.proto"; +import "frameworks/base/core/proto/android/server/job/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"; @@ -146,9 +148,9 @@ message Atom { VibratorStateChanged vibrator_state_changed = 84; DeferredJobStatsReported deferred_job_stats_reported = 85; ThermalThrottlingStateChanged thermal_throttling = 86; - FingerprintAcquired fingerprint_acquired = 87; - FingerprintAuthenticated fingerprint_authenticated = 88; - FingerprintErrorOccurred fingerprint_error_occurred = 89; + BiometricAcquired biometric_acquired = 87; + BiometricAuthenticated biometric_authenticated = 88; + BiometricErrorOccurred biometric_error_occurred = 89; Notification notification = 90; BatteryHealthSnapshot battery_health_snapshot = 91; SlowIo slow_io = 92; @@ -163,7 +165,7 @@ message Atom { // Consider removing this if it becomes a problem ServiceStateChanged service_state_changed = 99; ServiceLaunchReported service_launch_reported = 100; - PhenotypeFlagStateChanged phenotype_flag_state_changed = 101; + FlagFlipUpdateOccurred flag_flip_update_occurred = 101; BinaryPushStateChanged binary_push_state_changed = 102; DevicePolicyEvent device_policy_event = 103; DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104; @@ -209,10 +211,14 @@ message Atom { AdbConnectionChanged adb_connection_changed = 144; SpeechDspStatReported speech_dsp_stat_reported = 145; UsbContaminantReported usb_contaminant_reported = 146; + WatchdogRollbackOccurred watchdog_rollback_occurred = 147; + BiometricHalDeathReported biometric_hal_death_reported = 148; + BubbleUIChanged bubble_ui_changed = 149; + ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150; } // Pulled events will start at field 10000. - // Next: 10043 + // Next: 10048 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -245,7 +251,7 @@ message Atom { CategorySize category_size = 10028; ProcStats proc_stats = 10029; BatteryVoltage battery_voltage = 10030; - NumFingerprints num_fingerprints = 10031; + NumBiometricsEnrolled num_fingerprints_enrolled = 10031; DiskIo disk_io = 10032; PowerProfile power_profile = 10033; ProcStatsPkgProc proc_stats_pkg_proc = 10034; @@ -260,6 +266,9 @@ message Atom { BatteryLevel battery_level = 10043; BuildInformation build_information = 10044; BatteryCycleCount battery_cycle_count = 10045; + DebugElapsedClock debug_elapsed_clock = 10046; + DebugFailingElapsedClock debug_failing_elapsed_clock = 10047; + NumBiometricsEnrolled num_faces_enrolled = 10048; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -1483,6 +1492,25 @@ message BluetoothLinkLayerConnectionEvent { optional android.bluetooth.hci.StatusEnum reason_code = 9; } +/** + * Logs when a module is rolled back by Watchdog. + * + * Logged from: Rollback Manager + */ +message WatchdogRollbackOccurred { + enum RollbackType { + UNKNOWN = 0; + ROLLBACK_INITIATE = 1; + ROLLBACK_SUCCESS = 2; + ROLLBACK_FAILURE = 3; + } + optional RollbackType rollback_type = 1; + + optional string package_name = 2; + + optional int32 package_version_code = 3; +} + /** * Logs when something is plugged into or removed from the USB-C connector. @@ -2351,58 +2379,95 @@ message GenericAtom { } /** - * Logs when a fingerprint acquire event occurs. + * Logs when a biometric acquire event occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java + * frameworks/base/services/core/java/com/android/server/biometrics */ -message FingerprintAcquired { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this acquire is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; +message BiometricAcquired { + // Biometric modality that was acquired. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java. + optional int32 user = 2; + // If this acquire is for a crypto operation. e.g. Secure purchases, unlock password storage. + optional bool is_crypto = 3; + // Action that the device is performing. Acquired messages are only expected for enroll and + // authenticate. Other actions may indicate an error. + optional android.hardware.biometrics.ActionEnum action = 4; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 5; + // Acquired constants, e.g. ACQUIRED_GOOD. See constants defined by <Biometric>Manager. + optional int32 acquire_info = 6; + // Vendor-specific acquire info. Valid only if acquire_info == ACQUIRED_VENDOR. + optional int32 acquire_info_vendor = 7; } /** - * Logs when a fingerprint authentication event occurs. + * Logs when a biometric authentication event occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java - */ -message FingerprintAuthenticated { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this authentication is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; - // Whether or not this authentication was successful. - optional bool is_authenticated = 3; + * frameworks/base/services/core/java/com/android/server/biometrics + */ +message BiometricAuthenticated { + // Biometric modality that was used. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java + optional int32 user = 2; + // If this authentication is for a crypto operation. e.g. Secure purchases, unlock password + // storage. + optional bool is_crypto = 3; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 4; + + enum State { + UNKNOWN = 0; + REJECTED = 1; + PENDING_CONFIRMATION = 2; + CONFIRMED = 3; + } + + // State of the current auth attempt. + optional State state = 5; + // Time it took to authenticate. For BiometricPrompt where setRequireConfirmation(false) is + // specified and supported by the biometric modality, this is from the first ACQUIRED_GOOD to + // AUTHENTICATED. for setRequireConfirmation(true), this is from PENDING_CONFIRMATION to + // CONFIRMED. + optional int64 latency_millis = 6; } /** - * Logs when a fingerprint error occurs. + * Logs when a biometric error occurs. * * Logged from: - * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java + * frameworks/base/services/core/java/com/android/server/biometrics + */ +message BiometricErrorOccurred { + // Biometric modality that was used. + optional android.hardware.biometrics.ModalityEnum modality = 1; + // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java + optional int32 user = 2; + // If this error is for a crypto operation. e.g. Secure purchases, unlock password storage. + optional bool is_crypto = 3; + // Action that the device is performing. + optional android.hardware.biometrics.ActionEnum action = 4; + // The client that this acquisition was received for. + optional android.hardware.biometrics.ClientEnum client = 5; + // Error constants. See constants defined by <Biometric>Manager. Enums won't work since errors + // are unique to modality. + optional int32 error_info = 6; + // Vendor-specific error info. Valid only if acquire_info == ACQUIRED_VENDOR. These are defined + // by the vendor and not specified by the HIDL interface. + optional int32 error_info_vendor = 7; +} + +/** + * Logs when a biometric HAL has crashed. + * Logged from: + * frameworks/base/services/core/java/com/android/server/biometrics */ -message FingerprintErrorOccurred { - // The associated user. Eg: 0 for owners, 10+ for others. - // Defined in android/os/UserHandle.java - optional int32 user = 1; - // If this error is for a crypto fingerprint. - // e.g. Secure purchases, unlock password storage. - optional bool is_crypto = 2; - - enum Error { - UNKNOWN = 0; - LOCKOUT = 1; - PERMANENT_LOCKOUT = 2; - } - // The type of error. - optional Error error = 3; +message BiometricHalDeathReported { + // Biometric modality. + optional android.hardware.biometrics.ModalityEnum modality = 1; } message Notification { @@ -2476,18 +2541,14 @@ message Notification { } /* - * Logs when a flag flip state changes. - * Logged in P/h. + * Logs when a flag flip update occurrs. Used for mainline modules that update via flag flips. */ -message PhenotypeFlagStateChanged { - // Mendel configuration name. - optional string mendel_config_name = 1; - // State - enum State { - STATE_UNKNOWN = 0; - COMMITTED = 1; - } - optional State state = 2; +message FlagFlipUpdateOccurred { + // If the event is from a flag config package, specify the package name. + optional string flag_flip_package_name = 1; + + // The order id of the package + optional int64 order_id = 2; } /* @@ -3349,14 +3410,14 @@ message DiskIo { /** * Pulls the number of fingerprints for each user. * - * Pulled from StatsCompanionService, which queries FingerprintManager. + * Pulled from StatsCompanionService, which queries <Biometric>Manager. */ -message NumFingerprints { +message NumBiometricsEnrolled { // The associated user. Eg: 0 for owners, 10+ for others. // Defined in android/os/UserHandle.java optional int32 user = 1; // Number of fingerprints registered to that user. - optional int32 num_fingerprints = 2; + optional int32 num_enrolled = 2; } message AggStats { @@ -4557,3 +4618,107 @@ message UsbContaminantReported { optional string id = 1; optional android.service.usb.ContaminantPresenceStatus status = 2; } + +/** + * This atom is for debugging purpose. + */ +message DebugElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; + + enum Type { + TYPE_UNKNOWN = 0; + ALWAYS_PRESENT = 1; + PRESENT_ON_ODD_PULLS = 2; + } + // Type of behavior for the pulled data. + optional Type type = 5; +} + +/** + * This atom is for debugging purpose. + */ +message DebugFailingElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; +} + +/** Logs System UI bubbles event changed. + * + * Logged from: + * frameworks/base/packages/SystemUI/src/com/android/systemui/bubbles + */ +message BubbleUIChanged { + + // The app package that is posting the bubble. + optional string package_name = 1; + + // The notification channel that is posting the bubble. + optional string notification_channel = 2; + + // The notification id associated with the posted bubble. + optional int32 notification_id = 3; + + // The position of the bubble within the bubble stack. + optional int32 position = 4; + + // The total number of bubbles within the bubble stack. + optional int32 total_number = 5; + + // User interactions with the bubble. + enum Action { + UNKNOWN = 0; + POSTED = 1; + UPDATED = 2; + EXPANDED = 3; + COLLAPSED = 4; + DISMISSED = 5; + STACK_DISMISSED = 6; + STACK_MOVED = 7; + HEADER_GO_TO_APP = 8; + HEADER_GO_TO_SETTINGS = 9; + PERMISSION_OPT_IN = 10; + PERMISSION_OPT_OUT = 11; + PERMISSION_IGNORED = 12; + SWIPE_LEFT = 13; + SWIPE_RIGHT = 14; + } + optional Action action = 6; + + // Normalized screen position of the bubble stack. The range is between 0 and 1. + optional float normalized_x_position = 7; + optional float normalized_y_position = 8; +} + +/** + * Logs that a constraint for a scheduled job has changed. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/job/controllers/JobStatus.java + */ +message ScheduledJobConstraintChanged { + repeated AttributionNode attribution_node = 1; + + // Name of the job. + optional string job_name = 2; + + optional com.android.server.job.ConstraintEnum constraint = 3; + + enum State { + UNSATISFIED = 0; + SATISFIED = 1; + } + optional State state = 4; +} diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 5fea90b61c80..aa22333ab26c 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -128,6 +128,17 @@ void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { mConfigReceivers.erase(key); } +void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, + const sp<IBinder>& intentSender) { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers[uid] = intentSender; +} + +void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers.erase(uid); +} + void ConfigManager::RemoveConfig(const ConfigKey& key) { vector<sp<ConfigListener>> broadcastList; { @@ -181,6 +192,11 @@ void ConfigManager::RemoveConfigs(int uid) { mConfigReceivers.erase(*it); } + auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); + if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { + mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); + } + mConfigs.erase(uidIt); for (const sp<ConfigListener>& listener : mListeners) { @@ -213,6 +229,7 @@ void ConfigManager::RemoveAllConfigs() { } mConfigReceivers.clear(); + mActiveConfigsChangedReceivers.clear(); for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } @@ -250,6 +267,17 @@ const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key } } +const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const { + lock_guard<mutex> lock(mMutex); + + auto it = mActiveConfigsChangedReceivers.find(uid); + if (it == mActiveConfigsChangedReceivers.end()) { + return nullptr; + } else { + return it->second; + } +} + void ConfigManager::Dump(FILE* out) { lock_guard<mutex> lock(mMutex); diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h index 122e669057b0..c064a519f597 100644 --- a/cmds/statsd/src/config/ConfigManager.h +++ b/cmds/statsd/src/config/ConfigManager.h @@ -82,6 +82,23 @@ public: void RemoveConfigReceiver(const ConfigKey& key); /** + * Sets the broadcast receiver that is notified whenever the list of active configs + * changes for this uid. + */ + void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender); + + /** + * Returns the broadcast receiver for active configs changed for this uid. + */ + + const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const; + + /** + * Erase any active configs changed broadcast receiver associated with this uid. + */ + void RemoveActiveConfigsChangedReceiver(const int uid); + + /** * A configuration was removed. * * Reports this to listeners. @@ -130,6 +147,12 @@ private: std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers; /** + * Each uid can be subscribed by up to one receiver to notify that the list of active configs + * for this uid has changed. The receiver is specified as IBinder from PendingIntent. + */ + std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers; + + /** * The ConfigListeners that will be told about changes. */ std::vector<sp<ConfigListener>> mListeners; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 7e56beeefbf6..ba7bcc437d74 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -175,8 +175,8 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {android::util::CATEGORY_SIZE, {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, // Number of fingerprints registered to each user. - {android::util::NUM_FINGERPRINTS, - {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}}, + {android::util::NUM_FINGERPRINTS_ENROLLED, + {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}}, // ProcStats. {android::util::PROC_STATS, {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}}, @@ -209,6 +209,14 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER, {.puller = new StatsCompanionServicePuller( android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, + // DebugElapsedClock. + {android::util::DEBUG_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}}, + // DebugFailingElapsedClock. + {android::util::DEBUG_FAILING_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, // BuildInformation. {android::util::BUILD_INFORMATION, {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index ee2fe8f5750f..8e7a58b40d0c 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -901,7 +901,6 @@ Landroid/os/ParcelableParcel;->getClassLoader()Ljava/lang/ClassLoader; Landroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel; Landroid/os/ParcelFileDescriptor;-><init>(Ljava/io/FileDescriptor;)V Landroid/os/ParcelFileDescriptor;->fromData([BLjava/lang/String;)Landroid/os/ParcelFileDescriptor; -Landroid/os/ParcelFileDescriptor;->getFile(Ljava/io/FileDescriptor;)Ljava/io/File; Landroid/os/ParcelFileDescriptor;->seekTo(J)J Landroid/os/PerformanceCollector;-><init>()V Landroid/os/PerformanceCollector;->beginSnapshot(Ljava/lang/String;)V diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 92f47e767d2e..e77e212cb46e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1808,6 +1808,29 @@ public class Activity extends ContextThemeWrapper mCalled = true; } + /** + * Called when activity gets or looses the top resumed position in the system. + * + * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} multiple activities can be resumed + * at the same time in multi-window and multi-display modes. This callback should be used + * instead of {@link #onResume()} as an indication that the activity can try to open + * exclusive-access devices like camera.</p> + * + * <p>It will always be delivered after the activity was resumed and before it is paused. In + * some cases it might be skipped and activity can go straight from {@link #onResume()} to + * {@link #onPause()} without receiving the top resumed state.</p> + * + * @param isTopResumedActivity {@code true} if it's the topmost resumed activity in the system, + * {@code false} otherwise. A call with this as {@code true} will + * always be followed by another one with {@code false}. + * + * @see #onResume() + * @see #onPause() + * @see #onWindowFocusChanged(boolean) + */ + public void onTopResumedActivityChanged(boolean isTopResumedActivity) { + } + void setVoiceInteractor(IVoiceInteractor voiceInteractor) { if (mVoiceInteractor != null) { for (Request activeRequest: mVoiceInteractor.getActiveRequests()) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ee3d27c7dac8..03a09ee04ea1 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -449,6 +449,14 @@ public final class ActivityThread extends ClientTransactionHandler { ViewRootImpl.ActivityConfigCallback configCallback; ActivityClientRecord nextIdle; + // Indicates whether this activity is currently the topmost resumed one in the system. + // This holds the last reported value from server. + boolean isTopResumedActivity; + // This holds the value last sent to the activity. This is needed, because an update from + // server may come at random time, but we always need to report changes between ON_RESUME + // and ON_PAUSE to the app. + boolean lastReportedTopResumedState; + ProfilerInfo profilerInfo; @UnsupportedAppUsage @@ -3295,16 +3303,14 @@ public final class ActivityThread extends ClientTransactionHandler { final boolean resumed = !r.paused; if (resumed) { r.activity.mTemporaryPause = true; - mInstrumentation.callActivityOnPause(r.activity); + performPauseActivityIfNeeded(r, "performNewIntents"); } checkAndBlockForNetworkAccess(); deliverNewIntents(r, intents); if (resumed) { - r.activity.performResume(false, "performNewIntents"); + performResumeActivity(token, false, "performNewIntents"); r.activity.mTemporaryPause = false; - } - - if (r.paused && andPause) { + } else if (andPause) { // In this case the activity was in the paused state when we delivered the intent, // to guarantee onResume gets called after onNewIntent we temporarily resume the // activity and pause again as the caller wanted. @@ -3957,6 +3963,8 @@ public final class ActivityThread extends ClientTransactionHandler { r.state = null; r.persistentState = null; r.setState(ON_RESUME); + + reportTopResumedActivityChanged(r, r.isTopResumedActivity); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException("Unable to resume activity " @@ -4111,6 +4119,45 @@ public final class ActivityThread extends ClientTransactionHandler { Looper.myQueue().addIdleHandler(new Idler()); } + + @Override + public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) { + ActivityClientRecord r = mActivities.get(token); + if (r == null || r.activity == null) { + Slog.w(TAG, "Not found target activity to report position change for token: " + token); + return; + } + + if (DEBUG_ORDER) { + Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r); + } + + if (r.isTopResumedActivity == onTop) { + throw new IllegalStateException("Activity top position already set to onTop=" + onTop); + } + + r.isTopResumedActivity = onTop; + + if (r.getLifecycleState() == ON_RESUME) { + reportTopResumedActivityChanged(r, onTop); + } else { + if (DEBUG_ORDER) { + Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState()); + } + } + } + + /** + * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed + * since the last report. + */ + private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop) { + if (r.lastReportedTopResumedState != onTop) { + r.lastReportedTopResumedState = onTop; + r.activity.onTopResumedActivityChanged(onTop); + } + } + @Override public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, String reason) { @@ -4202,6 +4249,10 @@ public final class ActivityThread extends ClientTransactionHandler { return; } + // Always reporting top resumed position loss when pausing an activity. If necessary, it + // will be restored in performResumeActivity(). + reportTopResumedActivityChanged(r, false /* onTop */); + try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index b5560331e7aa..da45054cbe51 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -78,7 +78,7 @@ public abstract class AppOpsManagerInternal { /** * Sets the app-ops mode for a certain app-op and uid. * - * <p>Similar as {@link AppOpsManager#setMode} but does not require the package manager to be + * <p>Similar as {@link AppOpsManager#setUidMode} but does not require the package manager to be * working. Hence this can be used very early during boot. * * <p>Only for internal callers. Does <u>not</u> verify that package name belongs to uid. @@ -88,4 +88,12 @@ public abstract class AppOpsManagerInternal { * @param mode The new mode to set. */ public abstract void setUidMode(int code, int uid, int mode); + + /** + * Set all {@link #setMode (package) modes} for this uid to the default value. + * + * @param code The app-op + * @param uid The uid + */ + public abstract void setAllPkgModesToDefault(int code, int uid); } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 07dbb6bee9fd..70badfae4a20 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -105,6 +105,16 @@ public abstract class ClientTransactionHandler { boolean isForward, String reason); /** + * Notify the activity about top resumed state change. + * @param token Target activity token. + * @param isTopResumedActivity Current state of the activity, {@code true} if it's the + * topmost resumed activity in the system, {@code false} otherwise. + * @param reason Reason for performing this operation. + */ + public abstract void handleTopResumedActivityChanged(IBinder token, + boolean isTopResumedActivity, String reason); + + /** * Stop the activity. * @param token Target activity token. * @param show Flag indicating whether activity is still shown. diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index fb65da14a087..412d7f45986c 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -277,6 +277,7 @@ interface IActivityManager { void unstableProviderDied(in IBinder connection); boolean isIntentSenderAnActivity(in IIntentSender sender); boolean isIntentSenderAForegroundService(in IIntentSender sender); + boolean isIntentSenderABroadcast(in IIntentSender sender); int startActivityAsUser(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 181accea2e6a..f522d71afd08 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -87,6 +88,12 @@ public class KeyguardManager { "android.app.action.CONFIRM_FRP_CREDENTIAL"; /** + * @hide + */ + public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE = + "android.app.extra.BIOMETRIC_PROMPT_BUNDLE"; + + /** * A CharSequence dialog title to show to the user when used with a * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. * @hide @@ -101,12 +108,6 @@ 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 @@ -124,44 +125,23 @@ public class KeyguardManager { public static final int RESULT_ALTERNATE = 1; /** - * Get an intent to prompt the user to confirm credentials (pin, pattern or password) - * for the current user of the device. The caller is expected to launch this activity using - * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for + * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)} + * + * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics + * if enrolled) for the current user of the device. The caller is expected to launch this + * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. * - * @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. **/ + @Deprecated @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) - public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { - 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; - } + public Intent createConfirmDeviceCredentialIntent(CharSequence title, + CharSequence description) { + 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/PendingIntent.java b/core/java/android/app/PendingIntent.java index 75d95b2c2508..55014ebf1fcb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -1113,6 +1113,19 @@ public final class PendingIntent implements Parcelable { /** * @hide + * Check whether this PendingIntent will launch an Activity. + */ + public boolean isBroadcast() { + try { + return ActivityManager.getService() + .isIntentSenderABroadcast(mTarget); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Return the Intent of this PendingIntent. */ @UnsupportedAppUsage diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 9dcd1228522b..9b66c928abc0 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -73,6 +73,11 @@ public final class StatsManager { */ public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE"; + /** + * Long array extra of the active configs for the uid that added those configs. + */ + public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = + "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; /** * Broadcast Action: Statsd has started. @@ -274,6 +279,43 @@ public final class StatsManager { } } + /** + * Registers the operation that is called whenever there is a change in which configs are + * active. This must be called each time statsd starts. This operation allows + * statsd to inform clients that they should pull data of the configs that are currently + * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs + * that are active and stop pulling data of configs that are no longer active. + * + * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber + * associated with the given subscriberId. May be null, in which case + * it removes any associated pending intent for this client. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service + */ + @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) + public void setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent) + throws StatsUnavailableException { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (pendingIntent == null) { + service.removeActiveConfigsChangedOperation(mContext.getOpPackageName()); + } else { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + service.setActiveConfigsChangedOperation(intentSender, + mContext.getOpPackageName()); + } + + } catch (RemoteException e) { + Slog.e(TAG, + "Failed to connect to statsd when registering active configs listener."); + throw new StatsUnavailableException("could not connect", e); + } catch (SecurityException e) { + throw new StatsUnavailableException(e.getMessage(), e); + } + } + } + // TODO: Temporary for backwards compatibility. Remove. /** * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 2dc225af6482..ee13164d7564 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -102,6 +102,7 @@ import android.net.IConnectivityManager; import android.net.IEthernetManager; import android.net.IIpMemoryStore; import android.net.IIpSecService; +import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.IpMemoryStore; import android.net.IpSecManager; @@ -328,6 +329,14 @@ final class SystemServiceRegistry { return new ConnectivityManager(context, service); }}); + registerService(Context.NETD_SERVICE, INetd.class, new StaticServiceFetcher<INetd>() { + @Override + public INetd createService() throws ServiceNotFoundException { + return INetd.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NETD_SERVICE)); + } + }); + registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class, new StaticServiceFetcher<NetworkStack>() { @Override diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl index 2964fbc0084b..cf62e8d625db 100644 --- a/core/java/android/app/role/IRoleManager.aidl +++ b/core/java/android/app/role/IRoleManager.aidl @@ -18,6 +18,8 @@ package android.app.role; import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManagerCallback; +import android.os.Bundle; +import android.telephony.IFinancialSmsCallback; /** * @hide @@ -52,4 +54,8 @@ interface IRoleManager { List<String> getHeldRolesFromController(in String packageName); String getDefaultSmsPackage(int userId); + /** + * Get filtered SMS messages for financial app. + */ + void getSmsMessagesForFinancialApp(in String callingPkg, in Bundle params, in IFinancialSmsCallback callback); } diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java new file mode 100644 index 000000000000..4064a02e54a8 --- /dev/null +++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java @@ -0,0 +1,112 @@ +/* + * 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; + +/** + * Top resumed activity changed callback. + * @hide + */ +public class TopResumedActivityChangeItem extends ClientTransactionItem { + + private boolean mOnTop; + + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem"); + client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem"); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + + // ObjectPoolItem implementation + + private TopResumedActivityChangeItem() {} + + /** Obtain an instance initialized with provided params. */ + public static TopResumedActivityChangeItem obtain(boolean onTop) { + TopResumedActivityChangeItem instance = + ObjectPool.obtain(TopResumedActivityChangeItem.class); + if (instance == null) { + instance = new TopResumedActivityChangeItem(); + } + instance.mOnTop = onTop; + + return instance; + } + + @Override + public void recycle() { + mOnTop = false; + ObjectPool.recycle(this); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mOnTop); + } + + /** Read from Parcel. */ + private TopResumedActivityChangeItem(Parcel in) { + mOnTop = in.readBoolean(); + } + + public static final Creator<TopResumedActivityChangeItem> CREATOR = + new Creator<TopResumedActivityChangeItem>() { + public TopResumedActivityChangeItem createFromParcel(Parcel in) { + return new TopResumedActivityChangeItem(in); + } + + public TopResumedActivityChangeItem[] newArray(int size) { + return new TopResumedActivityChangeItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final TopResumedActivityChangeItem other = (TopResumedActivityChangeItem) o; + return mOnTop == other.mOnTop; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mOnTop ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "TopResumedActivityChangeItem{onTop=" + mOnTop + "}"; + } +} diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 171c2f5b1a08..c4bf1ebd1a6f 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -434,7 +434,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override - public int getConnectionState(BluetoothDevice device) { + public @BtProfileState int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); try { mServiceLock.readLock().lock(); @@ -689,7 +689,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage - public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); try { mServiceLock.readLock().lock(); diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java index 78560d2de420..2cb7b2d3c7e9 100644 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ b/core/java/android/bluetooth/BluetoothCodecStatus.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -42,7 +43,7 @@ public final class BluetoothCodecStatus implements Parcelable { public static final String EXTRA_CODEC_STATUS = "android.bluetooth.codec.extra.CODEC_STATUS"; - private final BluetoothCodecConfig mCodecConfig; + private final @Nullable BluetoothCodecConfig mCodecConfig; private final BluetoothCodecConfig[] mCodecsLocalCapabilities; private final BluetoothCodecConfig[] mCodecsSelectableCapabilities; @@ -140,7 +141,7 @@ public final class BluetoothCodecStatus implements Parcelable { * @return the current codec configuration */ @UnsupportedAppUsage - public BluetoothCodecConfig getCodecConfig() { + public @Nullable BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index b8670dbeadad..ef775967f8a7 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -18,11 +18,14 @@ package android.bluetooth; import android.Manifest; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -60,6 +63,16 @@ public interface BluetoothProfile { /** The profile is in disconnecting state */ int STATE_DISCONNECTING = 3; + /** @hide */ + @IntDef({ + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileState {} + /** * Headset and Handsfree profile */ diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 6704a45fb07a..47a4a2dca73d 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -87,6 +87,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * <p>For more information about using a ContentResolver with content providers, read the * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> * developer guide.</p> + * </div> */ public abstract class ContentResolver implements ContentInterface { /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 280f1ac9c067..87f9e464cdc2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3637,6 +3637,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.net.INetd} for communicating with the network stack + * @hide + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String NETD_SERVICE = "netd"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link NetworkStack} for communicating with the network stack * @hide * @see #getSystemService(String) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 9e7aaf652a4d..22f73dbd4644 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2375,8 +2375,7 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PACKAGE_ENABLE_ROLLBACK = "android.intent.action.PACKAGE_ENABLE_ROLLBACK"; /** - * Broadcast Action: An existing version of an application package has been - * rolled back to a previous version. + * Broadcast Action: A rollback has been committed. * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -2385,8 +2384,8 @@ public class Intent implements Parcelable, Cloneable { */ @SystemApi @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = - "android.intent.action.PACKAGE_ROLLBACK_EXECUTED"; + public static final String ACTION_ROLLBACK_COMMITTED = + "android.intent.action.ROLLBACK_COMMITTED"; /** * @hide * Broadcast Action: Ask system services if there is any reason to diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl index 2faf3adb34b6..4f4c34b88cbd 100644 --- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl +++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl @@ -27,5 +27,4 @@ import android.os.RemoteCallback; */ oneway interface IRuntimePermissionPresenter { void getAppPermissions(String packageName, in RemoteCallback callback); - void revokeRuntimePermission(String packageName, String permissionName); } diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl index 420bcb69e0c4..226d76abc01c 100644 --- a/core/java/android/content/rollback/IRollbackManager.aidl +++ b/core/java/android/content/rollback/IRollbackManager.aidl @@ -17,20 +17,16 @@ package android.content.rollback; import android.content.pm.ParceledListSlice; -import android.content.pm.StringParceledListSlice; import android.content.rollback.RollbackInfo; import android.content.IntentSender; /** {@hide} */ interface IRollbackManager { - RollbackInfo getAvailableRollback(String packageName); - - StringParceledListSlice getPackagesWithAvailableRollbacks(); - + ParceledListSlice getAvailableRollbacks(); ParceledListSlice getRecentlyExecutedRollbacks(); - void executeRollback(in RollbackInfo rollback, String callerPackageName, + void commitRollback(int rollbackId, String callerPackageName, in IntentSender statusReceiver); // Exposed for use from the system server only. Callback from the package diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java index 0803a7c1d651..812f995675fc 100644 --- a/core/java/android/content/rollback/RollbackInfo.java +++ b/core/java/android/content/rollback/RollbackInfo.java @@ -17,9 +17,12 @@ package android.content.rollback; import android.annotation.SystemApi; +import android.content.pm.PackageInstaller; import android.os.Parcel; import android.os.Parcelable; +import java.util.List; + /** * Information about a set of packages that can be, or already have been * rolled back together. @@ -34,25 +37,17 @@ public final class RollbackInfo implements Parcelable { */ private final int mRollbackId; - /** - * The package that needs to be rolled back. - */ - public final PackageRollbackInfo targetPackage; - - // TODO: Add a list of additional packages rolled back due to atomic - // install dependencies when rollback of atomic installs is supported. - // TODO: Add a flag to indicate if reboot is required, when rollback of - // staged installs is supported. + private final List<PackageRollbackInfo> mPackages; /** @hide */ - public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) { + public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) { this.mRollbackId = rollbackId; - this.targetPackage = targetPackage; + this.mPackages = packages; } private RollbackInfo(Parcel in) { mRollbackId = in.readInt(); - targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in); + mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR); } /** @@ -62,6 +57,31 @@ public final class RollbackInfo implements Parcelable { return mRollbackId; } + /** + * Returns the list of package that are rolled back. + */ + public List<PackageRollbackInfo> getPackages() { + return mPackages; + } + + /** + * Returns true if this rollback requires reboot to take effect after + * being committed. + */ + public boolean isStaged() { + // TODO: Support rollback of staged installs. + return false; + } + + /** + * Returns the session ID for the committed rollback for staged rollbacks. + * Only applicable for rollbacks that have been committed. + */ + public int getSessionId() { + // TODO: Support rollback of staged installs. + return PackageInstaller.SessionInfo.INVALID_ID; + } + @Override public int describeContents() { return 0; @@ -70,7 +90,7 @@ public final class RollbackInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mRollbackId); - targetPackage.writeToParcel(out, flags); + out.writeTypedList(mPackages); } public static final Parcelable.Creator<RollbackInfo> CREATOR = diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java index c1c0bc1d3e07..f8abcfc8fc76 100644 --- a/core/java/android/content/rollback/RollbackManager.java +++ b/core/java/android/content/rollback/RollbackManager.java @@ -17,7 +17,6 @@ package android.content.rollback; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -50,55 +49,26 @@ public final class RollbackManager { } /** - * Returns the rollback currently available to be executed for the given - * package. - * <p> - * The returned RollbackInfo describes what packages would be rolled back, - * including package version codes before and after rollback. The rollback - * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}. - * <p> - * TODO: What if there is no package installed on device for packageName? + * Returns a list of all currently available rollbacks. * - * @param packageName name of the package to get the availble RollbackInfo for. - * @return the rollback available for the package, or null if no rollback - * is available for the package. * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) { + public List<RollbackInfo> getAvailableRollbacks() { try { - return mBinder.getAvailableRollback(packageName); + return mBinder.getAvailableRollbacks().getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Gets the names of packages that are available for rollback. - * Call {@link #getAvailableRollback(String)} to get more information - * about the rollback available for a particular package. - * - * @return the names of packages that are available for rollback. - * @throws SecurityException if the caller does not have the - * MANAGE_ROLLBACKS permission. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @NonNull List<String> getPackagesWithAvailableRollbacks() { - try { - return mBinder.getPackagesWithAvailableRollbacks().getList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - - /** - * Gets the list of all recently executed rollbacks. + * Gets the list of all recently committed rollbacks. * This is for the purposes of preventing re-install of a bad version of a - * package. + * package and monitoring the status of a staged rollback. * <p> - * Returns an empty list if there are no recently executed rollbacks. + * Returns an empty list if there are no recently committed rollbacks. * <p> * To avoid having to keep around complete rollback history forever on a * device, the returned list of rollbacks is only guaranteed to include @@ -107,12 +77,12 @@ public final class RollbackManager { * (without the possibility of rollback) to a higher version code than was * rolled back from. * - * @return the recently executed rollbacks + * @return the recently committed rollbacks * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() { + public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() { try { return mBinder.getRecentlyExecutedRollbacks().getList(); } catch (RemoteException e) { @@ -121,28 +91,26 @@ public final class RollbackManager { } /** - * Execute the given rollback, rolling back all versions of the packages - * to the last good versions previously installed on the device as - * specified in the given rollback object. The rollback will fail if any - * of the installed packages or available rollbacks are inconsistent with - * the versions specified in the given rollback object, which can happen - * if a package has been updated or a rollback expired since the rollback - * object was retrieved from {@link #getAvailableRollback(String)}. + * Commit the rollback with given id, rolling back all versions of the + * packages to the last good versions previously installed on the device + * as specified in the corresponding RollbackInfo object. The + * rollback will fail if any of the installed packages or available + * rollbacks are inconsistent with the versions specified in the given + * rollback object, which can happen if a package has been updated or a + * rollback expired since the rollback object was retrieved from + * {@link #getAvailableRollbacks()}. * <p> * TODO: Specify the returns status codes. - * TODO: What happens in case reboot is required for the rollback to take - * effect for staged installs? * - * @param rollback to execute + * @param rollbackId ID of the rollback to commit * @param statusReceiver where to deliver the results * @throws SecurityException if the caller does not have the * MANAGE_ROLLBACKS permission. */ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) - public void executeRollback(@NonNull RollbackInfo rollback, - @NonNull IntentSender statusReceiver) { + public void commitRollback(int rollbackId, @NonNull IntentSender statusReceiver) { try { - mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver); + mBinder.commitRollback(rollbackId, mCallerPackageName, statusReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index 8bcaa4526fa7..b44458a7d449 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -16,17 +16,20 @@ package android.database; +import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.UserHandle; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; @@ -72,6 +75,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { @UnsupportedAppUsage private Uri mNotifyUri; + private List<Uri> mNotifyUris; private final Object mSelfObserverLock = new Object(); private ContentObserver mSelfObserver; @@ -155,7 +159,11 @@ public abstract class AbstractCursor implements CrossProcessCursor { @Override public boolean requery() { if (mSelfObserver != null && mSelfObserverRegistered == false) { - mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); + final int size = mNotifyUris.size(); + for (int i = 0; i < size; ++i) { + final Uri notifyUri = mNotifyUris.get(i); + mContentResolver.registerContentObserver(notifyUri, true, mSelfObserver); + } mSelfObserverRegistered = true; } mDataSetObservable.notifyChanged(); @@ -384,8 +392,12 @@ public abstract class AbstractCursor implements CrossProcessCursor { protected void onChange(boolean selfChange) { synchronized (mSelfObserverLock) { mContentObservable.dispatchChange(selfChange, null); - if (mNotifyUri != null && selfChange) { - mContentResolver.notifyChange(mNotifyUri, mSelfObserver); + if (mNotifyUris != null && selfChange) { + final int size = mNotifyUris.size(); + for (int i = 0; i < size; ++i) { + final Uri notifyUri = mNotifyUris.get(i); + mContentResolver.notifyChange(notifyUri, mSelfObserver); + } } } } @@ -399,19 +411,33 @@ public abstract class AbstractCursor implements CrossProcessCursor { */ @Override public void setNotificationUri(ContentResolver cr, Uri notifyUri) { - setNotificationUri(cr, notifyUri, cr.getUserId()); + setNotificationUris(cr, Arrays.asList(notifyUri)); + } + + @Override + public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) { + Preconditions.checkNotNull(cr); + Preconditions.checkNotNull(notifyUris); + + setNotificationUris(cr, notifyUris, cr.getUserId()); } /** @hide - set the notification uri but with an observer for a particular user's view */ - public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) { + public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle) { synchronized (mSelfObserverLock) { - mNotifyUri = notifyUri; + mNotifyUris = notifyUris; + mNotifyUri = mNotifyUris.get(0); mContentResolver = cr; if (mSelfObserver != null) { mContentResolver.unregisterContentObserver(mSelfObserver); } mSelfObserver = new SelfContentObserver(this); - mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle); + final int size = mNotifyUris.size(); + for (int i = 0; i < size; ++i) { + final Uri notifyUri = mNotifyUris.get(i); + mContentResolver.registerContentObserver( + notifyUri, true, mSelfObserver, userHandle); + } mSelfObserverRegistered = true; } } @@ -424,6 +450,13 @@ public abstract class AbstractCursor implements CrossProcessCursor { } @Override + public List<Uri> getNotificationUris() { + synchronized (mSelfObserverLock) { + return mNotifyUris; + } + } + + @Override public boolean getWantsAllOnMoveCalls() { return false; } diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 5a4280e997cd..1379138b37c0 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -16,11 +16,14 @@ package android.database; +import android.annotation.NonNull; import android.content.ContentResolver; import android.net.Uri; import android.os.Bundle; import java.io.Closeable; +import java.util.Arrays; +import java.util.List; /** * This interface provides random read-write access to the result set returned @@ -421,7 +424,10 @@ public interface Cursor extends Closeable { /** * Register to watch a content URI for changes. This can be the URI of a specific data row (for * example, "content://my_provider_type/23"), or a a generic URI for a content type. - * + * + * <p>Calling this overrides any previous call to + * {@link #setNotificationUris(ContentResolver, List)}. + * * @param cr The content resolver from the caller's context. The listener attached to * this resolver will be notified. * @param uri The content URI to watch. @@ -429,6 +435,24 @@ public interface Cursor extends Closeable { void setNotificationUri(ContentResolver cr, Uri uri); /** + * Similar to {@link #setNotificationUri(ContentResolver, Uri)}, except this version allows + * to watch multiple content URIs for changes. + * + * <p>If this is not implemented, this is equivalent to calling + * {@link #setNotificationUri(ContentResolver, Uri)} with the first URI in {@code uris}. + * + * <p>Calling this overrides any previous call to + * {@link #setNotificationUri(ContentResolver, Uri)}. + * + * @param cr The content resolver from the caller's context. The listener attached to + * this resolver will be notified. + * @param uris The content URIs to watch. + */ + default void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> uris) { + setNotificationUri(cr, uris.get(0)); + } + + /** * Return the URI at which notifications of changes in this Cursor's data * will be delivered, as previously set by {@link #setNotificationUri}. * @return Returns a URI that can be used with @@ -439,6 +463,22 @@ public interface Cursor extends Closeable { Uri getNotificationUri(); /** + * Return the URIs at which notifications of changes in this Cursor's data + * will be delivered, as previously set by {@link #setNotificationUris}. + * + * <p>If this is not implemented, this is equivalent to calling {@link #getNotificationUri()}. + * + * @return Returns URIs that can be used with + * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver) + * ContentResolver.registerContentObserver} to find out about changes to this Cursor's + * data. May be null if no notification URI has been set. + */ + default List<Uri> getNotificationUris() { + final Uri notifyUri = getNotificationUri(); + return notifyUri == null ? null : Arrays.asList(notifyUri); + } + + /** * onMove() will only be called across processes if this method returns true. * @return whether all cursor movement should result in a call to onMove(). */ diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java index 0d27dfb872f0..c9cafafe4041 100644 --- a/core/java/android/database/CursorWrapper.java +++ b/core/java/android/database/CursorWrapper.java @@ -21,6 +21,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.os.Bundle; +import java.util.List; + /** * Wrapper class for Cursor that delegates all calls to the actual cursor object. The primary * use for this class is to extend a cursor while overriding only a subset of its methods. @@ -241,11 +243,21 @@ public class CursorWrapper implements Cursor { } @Override + public void setNotificationUris(ContentResolver cr, List<Uri> uris) { + mCursor.setNotificationUris(cr, uris); + } + + @Override public Uri getNotificationUri() { return mCursor.getNotificationUri(); } @Override + public List<Uri> getNotificationUris() { + return mCursor.getNotificationUris(); + } + + @Override public void unregisterContentObserver(ContentObserver observer) { mCursor.unregisterContentObserver(observer); } diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index d2c0e7be5900..5d4928cd4ab3 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -182,6 +182,38 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { } /** + * Queries whether the given buffer description is supported by the system. If this returns + * true, then the allocation may succeed until resource exhaustion occurs. If this returns + * false then this combination will never succeed. + * + * @param width The width in pixels of the buffer + * @param height The height in pixels of the buffer + * @param format The @Format of each pixel + * @param layers The number of layers in the buffer + * @param usage The @Usage flags describing how the buffer will be used + * @return True if the combination is supported, false otherwise. + */ + public static boolean isSupported(int width, int height, @Format int format, int layers, + @Usage long usage) { + if (!HardwareBuffer.isSupportedFormat(format)) { + throw new IllegalArgumentException("Invalid pixel format " + format); + } + if (width <= 0) { + throw new IllegalArgumentException("Invalid width " + width); + } + if (height <= 0) { + throw new IllegalArgumentException("Invalid height " + height); + } + if (layers <= 0) { + throw new IllegalArgumentException("Invalid layer count " + layers); + } + if (format == BLOB && height != 1) { + throw new IllegalArgumentException("Height must be 1 when using the BLOB format"); + } + return nIsSupported(width, height, format, layers, usage); + } + + /** * Private use only. See {@link #create(int, int, int, int, long)}. May also be * called from JNI using an already allocated native <code>HardwareBuffer</code>. */ @@ -386,4 +418,6 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { private static native int nGetLayers(long nativeObject); @FastNative private static native long nGetUsage(long nativeObject); + private static native boolean nIsSupported(int width, int height, int format, int layers, + long usage); } diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 9d37d9939127..209afb88ccd3 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -252,12 +252,55 @@ public interface BiometricFaceConstants { public static final int FACE_ACQUIRED_TOO_SIMILAR = 15; /** + * The magnitude of the pan angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The pan angle is defined as the angle swept out by the user’s face turning + * their neck left and right. The pan angle would be zero if the user faced the + * camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; + + /** + * The magnitude of the tilt angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The tilt angle is defined as the angle swept out by the user’s face looking up + * and down. The pan angle would be zero if the user faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; + + /** + * The magnitude of the roll angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The roll angle is defined as the angle swept out by the user’s face tilting their head + * towards their shoulders to the left and right. The pan angle would be zero if the user + * faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; + + /** + * The user’s face has been obscured by some object. + * + * The user should be informed to remove any objects from the line of sight from + * the sensor to the user’s face. + */ + public static final int FACE_ACQUIRED_FACE_OBSCURED = 19; + + /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. * * @hide */ - public static final int FACE_ACQUIRED_VENDOR = 16; + public static final int FACE_ACQUIRED_VENDOR = 20; /** * @hide diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8aac1bfa1f8d..5afe1a9309f4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -171,5 +171,39 @@ public class BiometricManager { Slog.w(TAG, "resetTimeout(): Service not connected"); } } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialSuccess() { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialSuccess(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected"); + } + } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialError(int error, String message) { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialError(error, message); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); + } + } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c69b68e4360a..ec62abaab6a2 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -77,6 +77,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; + /** + * @hide + */ + public static final String KEY_ENABLE_FALLBACK = "enable_fallback"; /** * Error/help message will show for this amount of time. @@ -242,6 +246,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * The user will first be prompted to authenticate with biometrics, but also given the + * option to authenticate with their device PIN, pattern, or password. + * @param enable When true, the prompt will fall back to ask for the user's device + * credentials (PIN, pattern, or password). + * @return + */ + public Builder setEnableFallback(boolean enable) { + mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. @@ -250,11 +266,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); + final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK); if (TextUtils.isEmpty(title) && !useDefaultTitle) { throw new IllegalArgumentException("Title must be set and non-empty"); - } else if (TextUtils.isEmpty(negative)) { + } else if (TextUtils.isEmpty(negative) && !enableFallback) { throw new IllegalArgumentException("Negative text must be set and non-empty"); + } else if (!TextUtils.isEmpty(negative) && enableFallback) { + throw new IllegalArgumentException("Can't have both negative button behavior" + + " and fallback enabled"); } return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); } @@ -514,6 +534,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } + if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) { + throw new IllegalArgumentException("Fallback not supported with crypto"); + } authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index de828f2d6757..e4336d171ab6 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -51,4 +51,12 @@ interface IBiometricService { // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) void resetTimeout(in byte [] token); + + // TODO(b/123378871): Remove when moved. + // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's + // setEnableFallback method, since there's no way for us to intercept onActivityResult. + // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult. + void onConfirmDeviceCredentialSuccess(); + // TODO(b/123378871): Remove when moved. + void onConfirmDeviceCredentialError(int error, String message); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 97583089736e..425b9561dbc1 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3721,6 +3721,59 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.RecommendedStreamConfiguration[]>("android.depth.availableRecommendedDepthStreamConfigurations", android.hardware.camera2.params.RecommendedStreamConfiguration[].class); /** + * <p>The available dynamic depth dataspace stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>These are output stream configurations for use with + * dataSpace DYNAMIC_DEPTH. The configurations are + * listed as <code>(format, width, height, input?)</code> tuples.</p> + * <p>Only devices that support depth output for at least + * the HAL_PIXEL_FORMAT_Y16 dense depth map along with + * HAL_PIXEL_FORMAT_BLOB with the same size or size with + * the same aspect ratio can have dynamic depth dataspace + * stream configuration. {@link CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE android.depth.depthIsExclusive} also + * needs to be set to FALSE.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDynamicDepthStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for dynamic depth output streams.</p> + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>The minimum frame duration of a stream (of a particular format, size) + * is the same regardless of whether the stream is input or output.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for dynamic depth streams.</p> + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>All dynamic depth output streams may have a nonzero stall + * duration.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** * <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 diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index c527ab4c6730..7877a4d51313 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1119,6 +1119,8 @@ public class CameraMetadataNative implements Parcelable { continue; } + // Dynamic depth streams involve alot of SW processing and currently cannot be + // recommended. StreamConfigurationMap map = null; switch (i) { case RecommendedStreamConfigurationMap.USECASE_PREVIEW: @@ -1127,28 +1129,44 @@ public class CameraMetadataNative implements Parcelable { map = new StreamConfigurationMap(scData.streamConfigurationArray, scData.minDurationArray, scData.stallDurationArray, /*depthconfiguration*/ null, /*depthminduration*/ null, - /*depthstallduration*/ null, /*highspeedvideoconfigurations*/ null, + /*depthstallduration*/ null, + /*dynamicDepthConfigurations*/ null, + /*dynamicDepthMinFrameDurations*/ null, + /*dynamicDepthStallDurations*/ null, + /*highspeedvideoconfigurations*/ null, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; case RecommendedStreamConfigurationMap.USECASE_RECORD: map = new StreamConfigurationMap(scData.streamConfigurationArray, scData.minDurationArray, scData.stallDurationArray, /*depthconfiguration*/ null, /*depthminduration*/ null, - /*depthstallduration*/ null, highSpeedVideoConfigurations, + /*depthstallduration*/ null, + /*dynamicDepthConfigurations*/ null, + /*dynamicDepthMinFrameDurations*/ null, + /*dynamicDepthStallDurations*/ null, + highSpeedVideoConfigurations, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; case RecommendedStreamConfigurationMap.USECASE_ZSL: map = new StreamConfigurationMap(scData.streamConfigurationArray, scData.minDurationArray, scData.stallDurationArray, depthScData.streamConfigurationArray, depthScData.minDurationArray, - depthScData.stallDurationArray, /*highSpeedVideoConfigurations*/ null, + depthScData.stallDurationArray, + /*dynamicDepthConfigurations*/ null, + /*dynamicDepthMinFrameDurations*/ null, + /*dynamicDepthStallDurations*/ null, + /*highSpeedVideoConfigurations*/ null, inputOutputFormatsMap, listHighResolution, supportsPrivate[i]); break; default: map = new StreamConfigurationMap(scData.streamConfigurationArray, scData.minDurationArray, scData.stallDurationArray, depthScData.streamConfigurationArray, depthScData.minDurationArray, - depthScData.stallDurationArray, /*highSpeedVideoConfigurations*/ null, + depthScData.stallDurationArray, + /*dynamicDepthConfigurations*/ null, + /*dynamicDepthMinFrameDurations*/ null, + /*dynamicDepthStallDurations*/ null, + /*highSpeedVideoConfigurations*/ null, /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]); } @@ -1206,6 +1224,12 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS); StreamConfigurationDuration[] depthStallDurations = getBase( CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS); + StreamConfiguration[] dynamicDepthConfigurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS); + StreamConfigurationDuration[] dynamicDepthMinFrameDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS); + StreamConfigurationDuration[] dynamicDepthStallDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS); HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase( CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS); ReprocessFormatsMap inputOutputFormatsMap = getBase( @@ -1214,7 +1238,8 @@ public class CameraMetadataNative implements Parcelable { return new StreamConfigurationMap( configurations, minFrameDurations, stallDurations, depthConfigurations, depthMinFrameDurations, depthStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, + dynamicDepthConfigurations, dynamicDepthMinFrameDurations, + dynamicDepthStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index dd052a8db1d9..a22e008a65fd 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -95,13 +95,17 @@ public final class StreamConfigurationMap { StreamConfiguration[] depthConfigurations, StreamConfigurationDuration[] depthMinFrameDurations, StreamConfigurationDuration[] depthStallDurations, + StreamConfiguration[] dynamicDepthConfigurations, + StreamConfigurationDuration[] dynamicDepthMinFrameDurations, + StreamConfigurationDuration[] dynamicDepthStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution) { this(configurations, minFrameDurations, stallDurations, depthConfigurations, depthMinFrameDurations, depthStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, - /*enforceImplementationDefined*/ true); + dynamicDepthConfigurations, dynamicDepthMinFrameDurations, + dynamicDepthStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, + listHighResolution, /*enforceImplementationDefined*/ true); } /** @@ -131,6 +135,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] depthConfigurations, StreamConfigurationDuration[] depthMinFrameDurations, StreamConfigurationDuration[] depthStallDurations, + StreamConfiguration[] dynamicDepthConfigurations, + StreamConfigurationDuration[] dynamicDepthMinFrameDurations, + StreamConfigurationDuration[] dynamicDepthStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution, @@ -163,6 +170,19 @@ public final class StreamConfigurationMap { "depthStallDurations"); } + if (dynamicDepthConfigurations == null) { + mDynamicDepthConfigurations = new StreamConfiguration[0]; + mDynamicDepthMinFrameDurations = new StreamConfigurationDuration[0]; + mDynamicDepthStallDurations = new StreamConfigurationDuration[0]; + } else { + mDynamicDepthConfigurations = checkArrayElementsNotNull(dynamicDepthConfigurations, + "dynamicDepthConfigurations"); + mDynamicDepthMinFrameDurations = checkArrayElementsNotNull( + dynamicDepthMinFrameDurations, "dynamicDepthMinFrameDurations"); + mDynamicDepthStallDurations = checkArrayElementsNotNull(dynamicDepthStallDurations, + "dynamicDepthStallDurations"); + } + if (highSpeedVideoConfigurations == null) { mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0]; } else { @@ -205,6 +225,15 @@ public final class StreamConfigurationMap { mDepthOutputFormats.put(config.getFormat(), mDepthOutputFormats.get(config.getFormat()) + 1); } + for (StreamConfiguration config : mDynamicDepthConfigurations) { + if (!config.isOutput()) { + // Ignoring input configs + continue; + } + + mDynamicDepthOutputFormats.put(config.getFormat(), + mDynamicDepthOutputFormats.get(config.getFormat()) + 1); + } if (configurations != null && enforceImplementationDefined && mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) { @@ -335,6 +364,8 @@ public final class StreamConfigurationMap { int dataspace = imageFormatToDataspace(format); if (dataspace == HAL_DATASPACE_DEPTH) { return mDepthOutputFormats.indexOfKey(internalFormat) >= 0; + } else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) { + return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0; } else { return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0; } @@ -446,7 +477,9 @@ public final class StreamConfigurationMap { boolean isFlexible = SurfaceUtils.isFlexibleConsumer(surface); StreamConfiguration[] configs = - surfaceDataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations; + surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations : + surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations : + mConfigurations; for (StreamConfiguration config : configs) { if (config.getFormat() == surfaceFormat && config.isOutput()) { // Matching format, either need exact size match, or a flexible consumer @@ -479,7 +512,9 @@ public final class StreamConfigurationMap { int dataspace = imageFormatToDataspace(format); StreamConfiguration[] configs = - dataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations; + dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations : + dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations : + mConfigurations; for (StreamConfiguration config : configs) { if ((config.getFormat() == internalFormat) && config.isOutput() && config.getSize().equals(size)) { @@ -992,6 +1027,12 @@ public final class StreamConfigurationMap { Arrays.equals(mMinFrameDurations, other.mMinFrameDurations) && Arrays.equals(mStallDurations, other.mStallDurations) && Arrays.equals(mDepthConfigurations, other.mDepthConfigurations) && + Arrays.equals(mDepthMinFrameDurations, other.mDepthMinFrameDurations) && + Arrays.equals(mDepthStallDurations, other.mDepthStallDurations) && + Arrays.equals(mDynamicDepthConfigurations, other.mDynamicDepthConfigurations) && + Arrays.equals(mDynamicDepthMinFrameDurations, + other.mDynamicDepthMinFrameDurations) && + Arrays.equals(mDynamicDepthStallDurations, other.mDynamicDepthStallDurations) && Arrays.equals(mHighSpeedVideoConfigurations, other.mHighSpeedVideoConfigurations); } @@ -1005,9 +1046,10 @@ public final class StreamConfigurationMap { public int hashCode() { // XX: do we care about order? return HashCodeHelpers.hashCodeGeneric( - mConfigurations, mMinFrameDurations, - mStallDurations, - mDepthConfigurations, mHighSpeedVideoConfigurations); + mConfigurations, mMinFrameDurations, mStallDurations, + mDepthConfigurations, mDepthMinFrameDurations, mDepthStallDurations, + mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations, + mDynamicDepthStallDurations, mHighSpeedVideoConfigurations); } // Check that the argument is supported by #getOutputFormats or #getInputFormats @@ -1022,6 +1064,10 @@ public final class StreamConfigurationMap { if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) { return format; } + } else if (internalDataspace == HAL_DATASPACE_DYNAMIC_DEPTH) { + if (mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0) { + return format; + } } else { if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) { return format; @@ -1245,6 +1291,7 @@ public final class StreamConfigurationMap { switch (format) { case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: + case ImageFormat.DEPTH_JPEG: return HAL_PIXEL_FORMAT_BLOB; case ImageFormat.DEPTH16: return HAL_PIXEL_FORMAT_Y16; @@ -1264,6 +1311,7 @@ public final class StreamConfigurationMap { * <li>ImageFormat.JPEG => HAL_DATASPACE_V0_JFIF * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_DATASPACE_DEPTH * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH + * <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH * <li>others => HAL_DATASPACE_UNKNOWN * </ul> * </p> @@ -1293,6 +1341,8 @@ public final class StreamConfigurationMap { case ImageFormat.DEPTH16: case ImageFormat.RAW_DEPTH: return HAL_DATASPACE_DEPTH; + case ImageFormat.DEPTH_JPEG: + return HAL_DATASPACE_DYNAMIC_DEPTH; default: return HAL_DATASPACE_UNKNOWN; } @@ -1343,12 +1393,16 @@ public final class StreamConfigurationMap { SparseIntArray formatsMap = !output ? mInputFormats : dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats : + dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats : highRes ? mHighResOutputFormats : mOutputFormats; int sizesCount = formatsMap.get(format); - if ( ((!output || dataspace == HAL_DATASPACE_DEPTH) && sizesCount == 0) || - (output && dataspace != HAL_DATASPACE_DEPTH && mAllOutputFormats.get(format) == 0)) { + if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || + dataspace == HAL_DATASPACE_DYNAMIC_DEPTH)) && sizesCount == 0) || + (output && (dataspace != HAL_DATASPACE_DEPTH && + dataspace != HAL_DATASPACE_DYNAMIC_DEPTH) && + mAllOutputFormats.get(format) == 0)) { // Only throw if this is really not supported at all throw new IllegalArgumentException("format not available"); } @@ -1357,9 +1411,13 @@ public final class StreamConfigurationMap { int sizeIndex = 0; StreamConfiguration[] configurations = - (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations; + (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : + (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : + mConfigurations; StreamConfigurationDuration[] minFrameDurations = - (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : mMinFrameDurations; + (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : + (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : + mMinFrameDurations; for (StreamConfiguration config : configurations) { int fmt = config.getFormat(); @@ -1386,7 +1444,21 @@ public final class StreamConfigurationMap { } } - if (sizeIndex != sizesCount) { + // Dynamic depth streams can have both fast and also high res modes. + if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH)) { + + if (sizeIndex > sizesCount) { + throw new AssertionError( + "Too many dynamic depth sizes (expected " + sizesCount + ", actual " + + sizeIndex + ")"); + } + + if (sizeIndex <= 0) { + sizes = new Size[0]; + } else { + sizes = Arrays.copyOf(sizes, sizeIndex); + } + } else if (sizeIndex != sizesCount) { throw new AssertionError( "Too few sizes (expected " + sizesCount + ", actual " + sizeIndex + ")"); } @@ -1409,6 +1481,10 @@ public final class StreamConfigurationMap { for (int j = 0; j < mDepthOutputFormats.size(); j++) { formats[i++] = depthFormatToPublic(mDepthOutputFormats.keyAt(j)); } + if (mDynamicDepthOutputFormats.size() > 0) { + // Only one publicly dynamic depth format is available. + formats[i++] = ImageFormat.DEPTH_JPEG; + } } if (formats.length != i) { throw new AssertionError("Too few formats " + i + ", expected " + formats.length); @@ -1451,11 +1527,13 @@ public final class StreamConfigurationMap { private StreamConfigurationDuration[] getDurations(int duration, int dataspace) { switch (duration) { case DURATION_MIN_FRAME: - return (dataspace == HAL_DATASPACE_DEPTH) ? - mDepthMinFrameDurations : mMinFrameDurations; + return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : + (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? + mDynamicDepthMinFrameDurations : mMinFrameDurations; case DURATION_STALL: - return (dataspace == HAL_DATASPACE_DEPTH) ? - mDepthStallDurations : mStallDurations; + return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations : + (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations : + mStallDurations; default: throw new IllegalArgumentException("duration was invalid"); } @@ -1467,6 +1545,7 @@ public final class StreamConfigurationMap { int size = formatsMap.size(); if (output) { size += mDepthOutputFormats.size(); + size += mDynamicDepthOutputFormats.size(); } return size; @@ -1486,10 +1565,11 @@ public final class StreamConfigurationMap { return false; } - private boolean isSupportedInternalConfiguration(int format, int dataspace, - Size size) { + private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) { StreamConfiguration[] configurations = - (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations; + (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : + (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : + mConfigurations; for (int i = 0; i < configurations.length; i++) { if (configurations[i].getFormat() == format && @@ -1681,6 +1761,8 @@ public final class StreamConfigurationMap { return "DEPTH16"; case ImageFormat.DEPTH_POINT_CLOUD: return "DEPTH_POINT_CLOUD"; + case ImageFormat.DEPTH_JPEG: + return "DEPTH_JPEG"; case ImageFormat.RAW_DEPTH: return "RAW_DEPTH"; case ImageFormat.PRIVATE: @@ -1712,6 +1794,7 @@ public final class StreamConfigurationMap { (1 << HAL_DATASPACE_RANGE_SHIFT); private static final int HAL_DATASPACE_DEPTH = 0x1000; + private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002; private static final long DURATION_20FPS_NS = 50000000L; /** @@ -1728,6 +1811,10 @@ public final class StreamConfigurationMap { private final StreamConfigurationDuration[] mDepthMinFrameDurations; private final StreamConfigurationDuration[] mDepthStallDurations; + private final StreamConfiguration[] mDynamicDepthConfigurations; + private final StreamConfigurationDuration[] mDynamicDepthMinFrameDurations; + private final StreamConfigurationDuration[] mDynamicDepthStallDurations; + private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations; private final ReprocessFormatsMap mInputOutputFormatsMap; @@ -1745,6 +1832,8 @@ public final class StreamConfigurationMap { private final SparseIntArray mInputFormats = new SparseIntArray(); /** internal format -> num depth output sizes mapping, for HAL_DATASPACE_DEPTH */ private final SparseIntArray mDepthOutputFormats = new SparseIntArray(); + /** internal format -> num dynamic depth output sizes mapping, for HAL_DATASPACE_DYNAMIC_DEPTH */ + private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray(); /** High speed video Size -> FPS range count mapping*/ private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap = new HashMap<Size, Integer>(); diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 7e45441c804a..f3ebd7f36fd6 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -20,6 +20,7 @@ import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ParceledListSlice; import android.content.res.Resources; +import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.display.DisplayManager.DisplayListener; import android.media.projection.IMediaProjection; @@ -80,12 +81,20 @@ public final class DisplayManagerGlobal { new ArrayList<DisplayListenerDelegate>(); private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); + private final ColorSpace mWideColorSpace; private int[] mDisplayIdCache; private int mWifiDisplayScanNestCount; private DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; + try { + mWideColorSpace = + ColorSpace.get( + ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** @@ -495,6 +504,17 @@ public final class DisplayManagerGlobal { } /** + * Gets the preferred wide gamut color space for all displays. + * The wide gamut color space is returned from composition pipeline + * based on hardware capability. + * + * @hide + */ + public ColorSpace getPreferredWideGamutColorSpace() { + return mWideColorSpace; + } + + /** * Sets the global brightness configuration for a given user. * * @hide diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index aae8afbcad49..5ea8bd67ce75 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -116,4 +116,9 @@ interface IDisplayManager { // Get the minimum brightness curve. Curve getMinimumBrightnessCurve(); + + // Gets the id of the preferred wide gamut color space for all displays. + // The wide gamut color space is returned from composition pipeline + // based on hardware capability. + int getPreferredWideGamutColorSpaceId(); } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 5b3ad77dbf43..333cfbd400dd 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -597,6 +597,7 @@ public class InputMethodService extends AbstractInputMethodService { Log.v(TAG, "Making IME window invisible"); } setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + applyVisibilityInInsetsConsumer(false /* setVisible */); onPreRenderedWindowVisibilityChanged(false /* setVisible */); } else { mShowInputFlags = 0; @@ -625,10 +626,10 @@ public class InputMethodService extends AbstractInputMethodService { ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { if (mIsPreRendered) { - // TODO: notify visibility to insets consumer. if (DEBUG) { Log.v(TAG, "Making IME window visible"); } + applyVisibilityInInsetsConsumer(true /* setVisible */); onPreRenderedWindowVisibilityChanged(true /* setVisible */); } else { showWindow(true); @@ -1887,10 +1888,23 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); } + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } + /** + * Notify {@link android.view.ImeInsetsSourceConsumer} if IME has been pre-rendered + * for current EditorInfo, when pre-rendering is enabled. + */ + private void maybeNotifyPreRendered() { + if (!mCanPreRender || !mIsPreRendered) { + return; + } + mPrivOps.reportPreRendered(getCurrentInputEditorInfo()); + } + + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; mDecorViewVisible = true; @@ -1942,6 +1956,18 @@ public class InputMethodService extends AbstractInputMethodService { } } + /** + * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when + * pre-rendering is enabled. + * @param setVisible {@code true} to make it visible, false to hide it. + */ + private void applyVisibilityInInsetsConsumer(boolean setVisible) { + if (!mIsPreRendered) { + return; + } + mPrivOps.applyImeVisibility(setVisible); + } + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); @@ -2081,6 +2107,7 @@ public class InputMethodService extends AbstractInputMethodService { // When IME is not pre-rendered, this will actually show the IME. if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } else { diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java index b5d822672e70..6c291c25dbdf 100644 --- a/core/java/android/net/DhcpResults.java +++ b/core/java/android/net/DhcpResults.java @@ -17,24 +17,39 @@ package android.net; import android.annotation.UnsupportedAppUsage; -import android.net.NetworkUtils; import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** * A simple object for retrieving the results of a DHCP request. * Optimized (attempted) for that jni interface - * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties. + * TODO: remove this class and replace with other existing constructs * @hide */ -public class DhcpResults extends StaticIpConfiguration { +public final class DhcpResults implements Parcelable { private static final String TAG = "DhcpResults"; @UnsupportedAppUsage + public LinkAddress ipAddress; + + @UnsupportedAppUsage + public InetAddress gateway; + + @UnsupportedAppUsage + public final ArrayList<InetAddress> dnsServers = new ArrayList<>(); + + @UnsupportedAppUsage + public String domains; + + @UnsupportedAppUsage public Inet4Address serverAddress; /** Vendor specific information (from RFC 2132). */ @@ -48,23 +63,36 @@ public class DhcpResults extends StaticIpConfiguration { @UnsupportedAppUsage public int mtu; - @UnsupportedAppUsage public DhcpResults() { super(); } - @UnsupportedAppUsage + /** + * Create a {@link StaticIpConfiguration} based on the DhcpResults. + */ + public StaticIpConfiguration toStaticIpConfiguration() { + final StaticIpConfiguration s = new StaticIpConfiguration(); + // All these except dnsServers are immutable, so no need to make copies. + s.ipAddress = ipAddress; + s.gateway = gateway; + s.dnsServers.addAll(dnsServers); + s.domains = domains; + return s; + } + public DhcpResults(StaticIpConfiguration source) { - super(source); + if (source != null) { + ipAddress = source.ipAddress; + gateway = source.gateway; + dnsServers.addAll(source.dnsServers); + domains = source.domains; + } } /** copy constructor */ - @UnsupportedAppUsage public DhcpResults(DhcpResults source) { - super(source); - + this(source == null ? null : source.toStaticIpConfiguration()); if (source != null) { - // All these are immutable, so no need to make copies. serverAddress = source.serverAddress; vendorInfo = source.vendorInfo; leaseDuration = source.leaseDuration; @@ -73,6 +101,14 @@ public class DhcpResults extends StaticIpConfiguration { } /** + * @see StaticIpConfiguration#getRoutes(String) + * @hide + */ + public List<RouteInfo> getRoutes(String iface) { + return toStaticIpConfiguration().getRoutes(iface); + } + + /** * Test if this DHCP lease includes vendor hint that network link is * metered, and sensitive to heavy data transfers. */ @@ -85,7 +121,11 @@ public class DhcpResults extends StaticIpConfiguration { } public void clear() { - super.clear(); + ipAddress = null; + gateway = null; + dnsServers.clear(); + domains = null; + serverAddress = null; vendorInfo = null; leaseDuration = 0; mtu = 0; @@ -111,20 +151,20 @@ public class DhcpResults extends StaticIpConfiguration { DhcpResults target = (DhcpResults)obj; - return super.equals((StaticIpConfiguration) obj) && - Objects.equals(serverAddress, target.serverAddress) && - Objects.equals(vendorInfo, target.vendorInfo) && - leaseDuration == target.leaseDuration && - mtu == target.mtu; + return toStaticIpConfiguration().equals(target.toStaticIpConfiguration()) + && Objects.equals(serverAddress, target.serverAddress) + && Objects.equals(vendorInfo, target.vendorInfo) + && leaseDuration == target.leaseDuration + && mtu == target.mtu; } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + */ public static final Creator<DhcpResults> CREATOR = new Creator<DhcpResults>() { public DhcpResults createFromParcel(Parcel in) { - DhcpResults dhcpResults = new DhcpResults(); - readFromParcel(dhcpResults, in); - return dhcpResults; + return readFromParcel(in); } public DhcpResults[] newArray(int size) { @@ -134,19 +174,26 @@ public class DhcpResults extends StaticIpConfiguration { /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); + toStaticIpConfiguration().writeToParcel(dest, flags); dest.writeInt(leaseDuration); dest.writeInt(mtu); NetworkUtils.parcelInetAddress(dest, serverAddress, flags); dest.writeString(vendorInfo); } - private static void readFromParcel(DhcpResults dhcpResults, Parcel in) { - StaticIpConfiguration.readFromParcel(dhcpResults, in); + @Override + public int describeContents() { + return 0; + } + + private static DhcpResults readFromParcel(Parcel in) { + final StaticIpConfiguration s = StaticIpConfiguration.CREATOR.createFromParcel(in); + final DhcpResults dhcpResults = new DhcpResults(s); dhcpResults.leaseDuration = in.readInt(); dhcpResults.mtu = in.readInt(); dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in); dhcpResults.vendorInfo = in.readString(); + return dhcpResults; } // Utils for jni population - false on success @@ -184,25 +231,70 @@ public class DhcpResults extends StaticIpConfiguration { return false; } - public boolean setServerAddress(String addrString) { - try { - serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString); - } catch (IllegalArgumentException|ClassCastException e) { - Log.e(TAG, "setServerAddress failed with addrString " + addrString); - return true; - } - return false; + public LinkAddress getIpAddress() { + return ipAddress; + } + + public void setIpAddress(LinkAddress ipAddress) { + this.ipAddress = ipAddress; + } + + public InetAddress getGateway() { + return gateway; + } + + public void setGateway(InetAddress gateway) { + this.gateway = gateway; + } + + public List<InetAddress> getDnsServers() { + return dnsServers; + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(InetAddress server) { + dnsServers.add(server); + } + + public String getDomains() { + return domains; + } + + public void setDomains(String domains) { + this.domains = domains; + } + + public Inet4Address getServerAddress() { + return serverAddress; + } + + public void setServerAddress(Inet4Address addr) { + serverAddress = addr; + } + + public int getLeaseDuration() { + return leaseDuration; } public void setLeaseDuration(int duration) { leaseDuration = duration; } + public String getVendorInfo() { + return vendorInfo; + } + public void setVendorInfo(String info) { vendorInfo = info; } - public void setDomains(String newDomains) { - domains = newDomains; + public int getMtu() { + return mtu; + } + + public void setMtu(int mtu) { + this.mtu = mtu; } } diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 23c8aa368d87..e519fdf65e50 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -257,7 +257,6 @@ public final class IpSecTransform implements AutoCloseable { * * @hide */ - @SystemApi public static class NattKeepaliveCallback { /** The specified {@code Network} is not connected. */ public static final int ERROR_INVALID_NETWORK = 1; @@ -288,7 +287,6 @@ public final class IpSecTransform implements AutoCloseable { * * @hide */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_IPSEC_TUNNELS, android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD @@ -331,7 +329,6 @@ public final class IpSecTransform implements AutoCloseable { * * @hide */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_IPSEC_TUNNELS, android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c996d01fe8e4..39db4fef78b5 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -21,8 +21,10 @@ import static android.system.OsConstants.AF_INET6; import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; +import android.net.shared.Inet4AddressUtils; import android.os.Build; import android.os.Parcel; +import android.system.ErrnoException; import android.system.Os; import android.util.Log; import android.util.Pair; @@ -39,8 +41,6 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; -import android.system.ErrnoException; - /** * Native methods for managing network interfaces. * @@ -177,119 +177,37 @@ public class NetworkUtils { FileDescriptor fd) throws IOException; /** - * @see #intToInet4AddressHTL(int) - * @deprecated Use either {@link #intToInet4AddressHTH(int)} - * or {@link #intToInet4AddressHTL(int)} + * @see Inet4AddressUtils#intToInet4AddressHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)} + * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)} */ @Deprecated @UnsupportedAppUsage public static InetAddress intToInetAddress(int hostAddress) { - return intToInet4AddressHTL(hostAddress); - } - - /** - * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4) - * - * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes, - * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead. - * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is - * lower-order IPv4 address byte - */ - public static Inet4Address intToInet4AddressHTL(int hostAddress) { - return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); + return Inet4AddressUtils.intToInet4AddressHTL(hostAddress); } /** - * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) - * @param hostAddress an int coding for an IPv4 address - */ - public static Inet4Address intToInet4AddressHTH(int hostAddress) { - byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), - (byte) (0xff & (hostAddress >> 16)), - (byte) (0xff & (hostAddress >> 8)), - (byte) (0xff & hostAddress) }; - - try { - return (Inet4Address) InetAddress.getByAddress(addressBytes); - } catch (UnknownHostException e) { - throw new AssertionError(); - } - } - - /** - * @see #inet4AddressToIntHTL(Inet4Address) - * @deprecated Use either {@link #inet4AddressToIntHTH(Inet4Address)} - * or {@link #inet4AddressToIntHTL(Inet4Address)} + * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address) + * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)} + * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)} */ @Deprecated public static int inetAddressToInt(Inet4Address inetAddr) throws IllegalArgumentException { - return inet4AddressToIntHTL(inetAddr); - } - - /** - * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304) - * - * <p>This conversion can help order IP addresses: considering the ordering - * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned - * integers with {@link Integer#toUnsignedLong}. - * @param inetAddr is an InetAddress corresponding to the IPv4 address - * @return the IP address as integer - */ - public static int inet4AddressToIntHTH(Inet4Address inetAddr) - throws IllegalArgumentException { - byte [] addr = inetAddr.getAddress(); - return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16) - | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff); - } - - /** - * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201) - * - * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, - * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead. - * @param inetAddr is an InetAddress corresponding to the IPv4 address - * @return the IP address as integer - */ - public static int inet4AddressToIntHTL(Inet4Address inetAddr) { - return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr)); + return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr); } /** - * @see #prefixLengthToV4NetmaskIntHTL(int) - * @deprecated Use either {@link #prefixLengthToV4NetmaskIntHTH(int)} - * or {@link #prefixLengthToV4NetmaskIntHTL(int)} + * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)} + * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)} */ @Deprecated @UnsupportedAppUsage public static int prefixLengthToNetmaskInt(int prefixLength) throws IllegalArgumentException { - return prefixLengthToV4NetmaskIntHTL(prefixLength); - } - - /** - * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000) - * @return the IPv4 netmask as an integer - */ - public static int prefixLengthToV4NetmaskIntHTH(int prefixLength) - throws IllegalArgumentException { - if (prefixLength < 0 || prefixLength > 32) { - throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)"); - } - // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1) - return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength); - } - - /** - * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff). - * - * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, - * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead. - * @return the IPv4 netmask as an integer - */ - public static int prefixLengthToV4NetmaskIntHTL(int prefixLength) - throws IllegalArgumentException { - return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength)); + return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength); } /** @@ -307,17 +225,13 @@ public class NetworkUtils { * @return the network prefix length * @throws IllegalArgumentException the specified netmask was not contiguous. * @hide + * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)} */ @UnsupportedAppUsage + @Deprecated public static int netmaskToPrefixLength(Inet4Address netmask) { - // inetAddressToInt returns an int in *network* byte order. - int i = Integer.reverseBytes(inetAddressToInt(netmask)); - int prefixLength = Integer.bitCount(i); - int trailingZeros = Integer.numberOfTrailingZeros(i); - if (trailingZeros != 32 - prefixLength) { - throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); - } - return prefixLength; + // This is only here because some apps seem to be using it (@UnsupportedAppUsage). + return Inet4AddressUtils.netmaskToPrefixLength(netmask); } @@ -408,16 +322,8 @@ public class NetworkUtils { */ @UnsupportedAppUsage public static int getImplicitNetmask(Inet4Address address) { - int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. - if (firstByte < 128) { - return 8; - } else if (firstByte < 192) { - return 16; - } else if (firstByte < 224) { - return 24; - } else { - return 32; // Will likely not end well for other reasons. - } + // Only here because it seems to be used by apps + return Inet4AddressUtils.getImplicitNetmask(address); } /** @@ -445,28 +351,6 @@ public class NetworkUtils { } /** - * Get a prefix mask as Inet4Address for a given prefix length. - * - * <p>For example 20 -> 255.255.240.0 - */ - public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) - throws IllegalArgumentException { - return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); - } - - /** - * Get the broadcast address for a given prefix. - * - * <p>For example 192.168.0.1/24 -> 192.168.0.255 - */ - public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) - throws IllegalArgumentException { - final int intBroadcastAddr = inet4AddressToIntHTH(addr) - | ~prefixLengthToV4NetmaskIntHTH(prefixLength); - return intToInet4AddressHTH(intBroadcastAddr); - } - - /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java index 3aa56b90251f..25bae3c57423 100644 --- a/core/java/android/net/StaticIpConfiguration.java +++ b/core/java/android/net/StaticIpConfiguration.java @@ -16,10 +16,11 @@ package android.net; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; -import android.net.LinkAddress; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import java.net.InetAddress; import java.util.ArrayList; @@ -46,17 +47,22 @@ import java.util.Objects; * * @hide */ -public class StaticIpConfiguration implements Parcelable { +@SystemApi +@TestApi +public final class StaticIpConfiguration implements Parcelable { + /** @hide */ @UnsupportedAppUsage public LinkAddress ipAddress; + /** @hide */ @UnsupportedAppUsage public InetAddress gateway; + /** @hide */ @UnsupportedAppUsage public final ArrayList<InetAddress> dnsServers; + /** @hide */ @UnsupportedAppUsage public String domains; - @UnsupportedAppUsage public StaticIpConfiguration() { dnsServers = new ArrayList<InetAddress>(); } @@ -79,6 +85,41 @@ public class StaticIpConfiguration implements Parcelable { domains = null; } + public LinkAddress getIpAddress() { + return ipAddress; + } + + public void setIpAddress(LinkAddress ipAddress) { + this.ipAddress = ipAddress; + } + + public InetAddress getGateway() { + return gateway; + } + + public void setGateway(InetAddress gateway) { + this.gateway = gateway; + } + + public List<InetAddress> getDnsServers() { + return dnsServers; + } + + public String getDomains() { + return domains; + } + + public void setDomains(String newDomains) { + domains = newDomains; + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(InetAddress server) { + dnsServers.add(server); + } + /** * Returns the network routes specified by this object. Will typically include a * directly-connected route for the IP address's local subnet and a default route. If the @@ -86,7 +127,6 @@ public class StaticIpConfiguration implements Parcelable { * route to the gateway as well. This configuration is arguably invalid, but it used to work * in K and earlier, and other OSes appear to accept it. */ - @UnsupportedAppUsage public List<RouteInfo> getRoutes(String iface) { List<RouteInfo> routes = new ArrayList<RouteInfo>(3); if (ipAddress != null) { @@ -107,6 +147,7 @@ public class StaticIpConfiguration implements Parcelable { * contained in the LinkProperties will not be a complete picture of the link's configuration, * because any configuration information that is obtained dynamically by the network (e.g., * IPv6 configuration) will not be included. + * @hide */ public LinkProperties toLinkProperties(String iface) { LinkProperties lp = new LinkProperties(); @@ -124,6 +165,7 @@ public class StaticIpConfiguration implements Parcelable { return lp; } + @Override public String toString() { StringBuffer str = new StringBuffer(); @@ -143,6 +185,7 @@ public class StaticIpConfiguration implements Parcelable { return str.toString(); } + @Override public int hashCode() { int result = 13; result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode()); @@ -168,12 +211,10 @@ public class StaticIpConfiguration implements Parcelable { } /** Implement the Parcelable interface */ - public static Creator<StaticIpConfiguration> CREATOR = + public static final Creator<StaticIpConfiguration> CREATOR = new Creator<StaticIpConfiguration>() { public StaticIpConfiguration createFromParcel(Parcel in) { - StaticIpConfiguration s = new StaticIpConfiguration(); - readFromParcel(s, in); - return s; + return readFromParcel(in); } public StaticIpConfiguration[] newArray(int size) { @@ -182,11 +223,13 @@ public class StaticIpConfiguration implements Parcelable { }; /** Implement the Parcelable interface */ + @Override public int describeContents() { return 0; } /** Implement the Parcelable interface */ + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(ipAddress, flags); NetworkUtils.parcelInetAddress(dest, gateway, flags); @@ -197,7 +240,9 @@ public class StaticIpConfiguration implements Parcelable { dest.writeString(domains); } - protected static void readFromParcel(StaticIpConfiguration s, Parcel in) { + /** @hide */ + public static StaticIpConfiguration readFromParcel(Parcel in) { + final StaticIpConfiguration s = new StaticIpConfiguration(); s.ipAddress = in.readParcelable(null); s.gateway = NetworkUtils.unparcelInetAddress(in); s.dnsServers.clear(); @@ -206,5 +251,6 @@ public class StaticIpConfiguration implements Parcelable { s.dnsServers.add(NetworkUtils.unparcelInetAddress(in)); } s.domains = in.readString(); + return s; } } diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java index f28cdc902848..73cf94b785a7 100644 --- a/core/java/android/net/apf/ApfCapabilities.java +++ b/core/java/android/net/apf/ApfCapabilities.java @@ -16,11 +16,16 @@ package android.net.apf; +import android.annotation.SystemApi; +import android.annotation.TestApi; + /** * APF program support capabilities. * * @hide */ +@SystemApi +@TestApi public class ApfCapabilities { /** * Version of APF instruction set supported for packet filtering. 0 indicates no support for diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java index 1634694cc71f..7432687e136f 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java @@ -17,11 +17,15 @@ package android.net.captiveportal; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; /** * Result of calling isCaptivePortal(). * @hide */ +@SystemApi +@TestApi public final class CaptivePortalProbeResult { public static final int SUCCESS_CODE = 204; public static final int FAILED_CODE = 599; diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java index 57a926afec54..7ad4ecf2264c 100644 --- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java +++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java @@ -21,21 +21,26 @@ import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** @hide */ +@SystemApi +@TestApi public abstract class CaptivePortalProbeSpec { - public static final String HTTP_LOCATION_HEADER_NAME = "Location"; - private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName(); private static final String REGEX_SEPARATOR = "@@/@@"; private static final String SPEC_SEPARATOR = "@@,@@"; @@ -55,7 +60,9 @@ public abstract class CaptivePortalProbeSpec { * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}. * @throws ParseException The string is empty, does not match the above format, or a regular * expression is invalid for {@link Pattern#compile(String)}. + * @hide */ + @VisibleForTesting @NonNull public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException, MalformedURLException { @@ -113,7 +120,8 @@ public abstract class CaptivePortalProbeSpec { * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}. * <p>This method does not throw but ignores any entry that could not be parsed. */ - public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) { + public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs( + String settingsVal) { List<CaptivePortalProbeSpec> specs = new ArrayList<>(); if (settingsVal != null) { for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) { @@ -128,7 +136,7 @@ public abstract class CaptivePortalProbeSpec { if (specs.isEmpty()) { Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal)); } - return specs.toArray(new CaptivePortalProbeSpec[specs.size()]); + return specs; } /** diff --git a/core/java/android/net/shared/Inet4AddressUtils.java b/core/java/android/net/shared/Inet4AddressUtils.java new file mode 100644 index 000000000000..bec0c84fa689 --- /dev/null +++ b/core/java/android/net/shared/Inet4AddressUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Collection of utilities to work with IPv4 addresses. + * @hide + */ +public class Inet4AddressUtils { + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4) + * + * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes, + * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead. + * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is + * lower-order IPv4 address byte + */ + public static Inet4Address intToInet4AddressHTL(int hostAddress) { + return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); + } + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) + * @param hostAddress an int coding for an IPv4 address + */ + public static Inet4Address intToInet4AddressHTH(int hostAddress) { + byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), + (byte) (0xff & (hostAddress >> 16)), + (byte) (0xff & (hostAddress >> 8)), + (byte) (0xff & hostAddress) }; + + try { + return (Inet4Address) InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + /** + * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304) + * + * <p>This conversion can help order IP addresses: considering the ordering + * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned + * integers with {@link Integer#toUnsignedLong}. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTH(Inet4Address inetAddr) + throws IllegalArgumentException { + byte [] addr = inetAddr.getAddress(); + return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16) + | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff); + } + + /** + * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201) + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTL(Inet4Address inetAddr) { + return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr)); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000) + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTH(int prefixLength) + throws IllegalArgumentException { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)"); + } + // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1) + return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff). + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead. + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTL(int prefixLength) + throws IllegalArgumentException { + return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + */ + public static int netmaskToPrefixLength(Inet4Address netmask) { + // inetAddressToInt returns an int in *network* byte order. + int i = inet4AddressToIntHTH(netmask); + int prefixLength = Integer.bitCount(i); + int trailingZeros = Integer.numberOfTrailingZeros(i); + if (trailingZeros != 32 - prefixLength) { + throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); + } + return prefixLength; + } + + /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + public static int getImplicitNetmask(Inet4Address address) { + int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. + if (firstByte < 128) { + return 8; + } else if (firstByte < 192) { + return 16; + } else if (firstByte < 224) { + return 24; + } else { + return 32; // Will likely not end well for other reasons. + } + } + + /** + * Get the broadcast address for a given prefix. + * + * <p>For example 192.168.0.1/24 -> 192.168.0.255 + */ + public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) + throws IllegalArgumentException { + final int intBroadcastAddr = inet4AddressToIntHTH(addr) + | ~prefixLengthToV4NetmaskIntHTH(prefixLength); + return intToInet4AddressHTH(intBroadcastAddr); + } + + /** + * Get a prefix mask as Inet4Address for a given prefix length. + * + * <p>For example 20 -> 255.255.240.0 + */ + public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) + throws IllegalArgumentException { + return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 954071a0ee97..3fc5e41ad990 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,6 +16,8 @@ package android.os; +import android.Manifest.permission; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; @@ -369,4 +371,26 @@ public class BatteryManager { throw e.rethrowFromSystemServer(); } } + + /** + * Sets the delay for reporting battery state as charging after device is plugged in. + * This allows machine-learning or heuristics to delay the reporting and the corresponding + * broadcast, based on battery level, charging rate, and/or other parameters. + * + * @param delayMillis the delay in milliseconds, negative value to reset. + * + * @return True if the delay was set successfully. + * + * @see ACTION_CHARGING + * @hide + */ + @RequiresPermission(permission.POWER_SAVER) + @SystemApi + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + try { + return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index eae1aa5eb750..1919fcc00a33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -834,10 +834,10 @@ public abstract class BatteryStats implements Parcelable { * also be bumped. */ static final String[] USER_ACTIVITY_TYPES = { - "other", "button", "touch", "accessibility" + "other", "button", "touch", "accessibility", "attention" }; - public static final int NUM_USER_ACTIVITY_TYPES = 4; + public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index d463b4422847..3a5b8a86204e 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -16,10 +16,12 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.IBinder.DeathRecipient; @@ -27,14 +29,14 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. * * @hide */ -// TODO: Expose API when the implementation is more complete. -// @SystemApi +@SystemApi @SystemService(Context.BUGREPORT_SERVICE) public class BugreportManager { private final Context mContext; @@ -47,47 +49,46 @@ public class BugreportManager { } /** - * An interface describing the listener for bugreport progress and status. + * An interface describing the callback for bugreport progress and status. */ - public interface BugreportListener { - /** - * Called when there is a progress update. - * @param progress the progress in [0.0, 100.0] - */ - void onProgress(float progress); - + public abstract static class BugreportCallback { + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, - BUGREPORT_ERROR_RUNTIME + BUGREPORT_ERROR_RUNTIME, + BUGREPORT_ERROR_USER_DENIED_CONSENT, + BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ - @interface BugreportErrorCode {} + public @interface BugreportErrorCode {} /** The input options were invalid */ - int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; + public static final int BUGREPORT_ERROR_INVALID_INPUT = + IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ - int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; + public static final int BUGREPORT_ERROR_RUNTIME = + IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ - int BUGREPORT_ERROR_USER_DENIED_CONSENT = + public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; /** The request to get user consent timed out. */ - int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = + public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + public void onProgress(float progress) {} + + /** * Called when taking bugreport resulted in an error. * - * @param errorCode the error that occurred. Possible values are - * {@code BUGREPORT_ERROR_INVALID_INPUT}, - * {@code BUGREPORT_ERROR_RUNTIME}, - * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}, - * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}. - * * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not * consent to sharing the bugreport with the calling app. * @@ -95,19 +96,19 @@ public class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. */ - void onError(@BugreportErrorCode int errorCode); + public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ - void onFinished(); + public void onFinished() {} } /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several - * seconds to return in the worst case. {@code listener} will receive progress and status + * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>The bugreport artifacts will be copied over to the given file descriptors only if the @@ -118,19 +119,23 @@ public class BugreportManager { * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param params options that specify what kind of a bugreport should be taken - * @param listener callback for progress and status updates + * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) - public void startBugreport(@NonNull FileDescriptor bugreportFd, - @Nullable FileDescriptor screenshotFd, - @NonNull BugreportParams params, @NonNull BugreportListener listener) { + public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, + @Nullable ParcelFileDescriptor screenshotFd, + @NonNull BugreportParams params, + @NonNull @CallbackExecutor Executor executor, + @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. - DumpstateListener dsListener = new DumpstateListener(listener); - + DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, - mContext.getOpPackageName(), bugreportFd, screenshotFd, + mContext.getOpPackageName(), + (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), + (screenshotFd != null + ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -151,10 +156,12 @@ public class BugreportManager { private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { - private final BugreportListener mListener; + private final Executor mExecutor; + private final BugreportCallback mCallback; - DumpstateListener(@Nullable BugreportListener listener) { - mListener = listener; + DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { + mExecutor = executor; + mCallback = callback; } @Override @@ -164,19 +171,37 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { - mListener.onProgress(progress); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onProgress(progress); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onError(int errorCode) throws RemoteException { - mListener.onError(errorCode); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onError(errorCode); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onFinished() throws RemoteException { + final long identity = Binder.clearCallingIdentity(); try { - mListener.onFinished(); + mExecutor.execute(() -> { + mCallback.onFinished(); + }); } finally { + Binder.restoreCallingIdentity(identity); // The bugreport has finished. Let's shutdown the service to minimize its footprint. cancelBugreport(); } diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 4e696aed1fa8..3871375cfced 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.IntDef; +import android.annotation.SystemApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,8 +27,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ -// TODO: Expose API when the implementation is more complete. -// @SystemApi +@SystemApi public final class BugreportParams { private final int mMode; diff --git a/core/java/android/os/ExternalVibration.aidl b/core/java/android/os/ExternalVibration.aidl new file mode 100644 index 000000000000..2629a401541d --- /dev/null +++ b/core/java/android/os/ExternalVibration.aidl @@ -0,0 +1,19 @@ +/* + * 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.os; + +parcelable ExternalVibration cpp_header "vibrator/ExternalVibration.h"; diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java new file mode 100644 index 000000000000..69ab1d94a9e2 --- /dev/null +++ b/core/java/android/os/ExternalVibration.java @@ -0,0 +1,171 @@ +/* + * 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.os; + +import android.annotation.NonNull; +import android.media.AudioAttributes; +import android.util.Slog; + +import com.android.internal.util.Preconditions; + +/** + * An ExternalVibration represents an on-going vibration being controlled by something other than + * the core vibrator service. + * + * @hide + */ +public class ExternalVibration implements Parcelable { + private static final String TAG = "ExternalVibration"; + private int mUid; + @NonNull + private String mPkg; + @NonNull + private AudioAttributes mAttrs; + @NonNull + private IExternalVibrationController mController; + // A token used to maintain equality comparisons when passing objects across process + // boundaries. + @NonNull + private IBinder mToken; + + public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs, + @NonNull IExternalVibrationController controller) { + mUid = uid; + mPkg = Preconditions.checkNotNull(pkg); + mAttrs = Preconditions.checkNotNull(attrs); + mController = Preconditions.checkNotNull(controller); + mToken = new Binder(); + } + + private ExternalVibration(Parcel in) { + mUid = in.readInt(); + mPkg = in.readString(); + mAttrs = readAudioAttributes(in); + mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder()); + mToken = in.readStrongBinder(); + } + + private AudioAttributes readAudioAttributes(Parcel in) { + int usage = in.readInt(); + int contentType = in.readInt(); + int capturePreset = in.readInt(); + int flags = in.readInt(); + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + return builder.setUsage(usage) + .setContentType(contentType) + .setCapturePreset(capturePreset) + .setFlags(flags) + .build(); + } + + public int getUid() { + return mUid; + } + + public String getPackage() { + return mPkg; + } + + public AudioAttributes getAudioAttributes() { + return mAttrs; + } + + /** + * Mutes the external vibration if it's playing and unmuted. + * + * @return whether the muting operation was successful + */ + public boolean mute() { + try { + mController.mute(); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e); + return false; + } + return true; + } + + /** + * Unmutes the external vibration if it's playing and muted. + * + * @return whether the unmuting operation was successful + */ + public boolean unmute() { + try { + mController.unmute(); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e); + return false; + } + return true; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof ExternalVibration)) { + return false; + } + ExternalVibration other = (ExternalVibration) o; + return mToken.equals(other.mToken); + } + + @Override + public String toString() { + return "ExternalVibration{" + + "uid=" + mUid + ", " + + "pkg=" + mPkg + ", " + + "attrs=" + mAttrs + ", " + + "controller=" + mController + + "token=" + mController + + "}"; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mUid); + out.writeString(mPkg); + writeAudioAttributes(mAttrs, out, flags); + out.writeParcelable(mAttrs, flags); + out.writeStrongBinder(mController.asBinder()); + out.writeStrongBinder(mToken); + } + + private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) { + out.writeInt(attrs.getUsage()); + out.writeInt(attrs.getContentType()); + out.writeInt(attrs.getCapturePreset()); + out.writeInt(attrs.getAllFlags()); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ExternalVibration> CREATOR = + new Parcelable.Creator<ExternalVibration>() { + @Override + public ExternalVibration createFromParcel(Parcel in) { + return new ExternalVibration(in); + } + + @Override + public ExternalVibration[] newArray(int size) { + return new ExternalVibration[size]; + } + }; +} diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java index dd85e1584f0e..da0389578e15 100644 --- a/core/java/android/os/FileObserver.java +++ b/core/java/android/os/FileObserver.java @@ -16,11 +16,15 @@ package android.os; +import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; +import java.io.File; import java.lang.ref.WeakReference; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; /** * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>) @@ -28,7 +32,7 @@ import java.util.HashMap; * the device (including this one). FileObserver is an abstract class; * subclasses must implement the event handler {@link #onEvent(int, String)}. * - * <p>Each FileObserver instance monitors a single file or directory. + * <p>Each FileObserver instance can monitor multiple files or directories. * If a directory is monitored, events will be triggered for all files and * subdirectories inside the monitored directory.</p> * @@ -86,21 +90,32 @@ public abstract class FileObserver { observe(m_fd); } - public int startWatching(String path, int mask, FileObserver observer) { - int wfd = startWatching(m_fd, path, mask); + public int[] startWatching(List<File> files, int mask, FileObserver observer) { + final int count = files.size(); + final String[] paths = new String[count]; + for (int i = 0; i < count; ++i) { + paths[i] = files.get(i).getAbsolutePath(); + } + final int[] wfds = new int[count]; + Arrays.fill(wfds, -1); + + startWatching(m_fd, paths, mask, wfds); - Integer i = new Integer(wfd); - if (wfd >= 0) { - synchronized (m_observers) { - m_observers.put(i, new WeakReference(observer)); + final WeakReference<FileObserver> fileObserverWeakReference = + new WeakReference<>(observer); + synchronized (m_observers) { + for (int wfd : wfds) { + if (wfd >= 0) { + m_observers.put(wfd, fileObserverWeakReference); + } } } - return i; + return wfds; } - public void stopWatching(int descriptor) { - stopWatching(m_fd, descriptor); + public void stopWatching(int[] descriptors) { + stopWatching(m_fd, descriptors); } public void onEvent(int wfd, int mask, String path) { @@ -129,8 +144,8 @@ public abstract class FileObserver { private native int init(); private native void observe(int fd); - private native int startWatching(int fd, String path, int mask); - private native void stopWatching(int fd, int wfd); + private native void startWatching(int fd, String[] paths, int mask, int[] wfds); + private native void stopWatching(int fd, int[] wfds); } private static ObserverThread s_observerThread; @@ -141,15 +156,34 @@ public abstract class FileObserver { } // instance - private String m_path; - private Integer m_descriptor; - private int m_mask; + private final List<File> mFiles; + private int[] mDescriptors; + private final int mMask; /** * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). + * + * @deprecated use {@link #FileObserver(File)} instead. */ + @Deprecated public FileObserver(String path) { - this(path, ALL_EVENTS); + this(new File(path)); + } + + /** + * Equivalent to FileObserver(file, FileObserver.ALL_EVENTS). + */ + public FileObserver(@NonNull File file) { + this(Arrays.asList(file)); + } + + /** + * Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS). + * + * @param files The files or directories to monitor + */ + public FileObserver(@NonNull List<File> files) { + this(files, ALL_EVENTS); } /** @@ -159,11 +193,36 @@ public abstract class FileObserver { * * @param path The file or directory to monitor * @param mask The event or events (added together) to watch for + * + * @deprecated use {@link #FileObserver(File, int)} instead. */ + @Deprecated public FileObserver(String path, int mask) { - m_path = path; - m_mask = mask; - m_descriptor = -1; + this(new File(path), mask); + } + + /** + * Create a new file observer for a certain file or directory. + * Monitoring does not start on creation! You must call + * {@link #startWatching()} before you will receive events. + * + * @param file The file or directory to monitor + * @param mask The event or events (added together) to watch for + */ + public FileObserver(@NonNull File file, int mask) { + this(Arrays.asList(file), mask); + } + + /** + * Version of {@link #FileObserver(File, int)} that allows callers to monitor + * multiple files or directories. + * + * @param files The files or directories to monitor + * @param mask The event or events (added together) to watch for + */ + public FileObserver(@NonNull List<File> files, int mask) { + mFiles = files; + mMask = mask; } protected void finalize() { @@ -176,8 +235,8 @@ public abstract class FileObserver { * If monitoring is already started, this call has no effect. */ public void startWatching() { - if (m_descriptor < 0) { - m_descriptor = s_observerThread.startWatching(m_path, m_mask, this); + if (mDescriptors == null) { + mDescriptors = s_observerThread.startWatching(mFiles, mMask, this); } } @@ -187,9 +246,9 @@ public abstract class FileObserver { * monitoring is already stopped, this call has no effect. */ public void stopWatching() { - if (m_descriptor >= 0) { - s_observerThread.stopWatching(m_descriptor); - m_descriptor = -1; + if (mDescriptors != null) { + s_observerThread.stopWatching(mDescriptors); + mDescriptors = null; } } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 51c3c4c25ce0..629289bb7a45 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -39,6 +39,7 @@ import static android.system.OsConstants.W_OK; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ContentResolver; import android.provider.DocumentsContract.Document; import android.system.ErrnoException; @@ -852,6 +853,7 @@ public class FileUtils { * * @hide */ + @TestApi public static boolean contains(File dir, File file) { if (dir == null || file == null) return false; return contains(dir.getAbsolutePath(), file.getAbsolutePath()); diff --git a/core/java/android/os/IExternalVibrationController.aidl b/core/java/android/os/IExternalVibrationController.aidl new file mode 100644 index 000000000000..56da8c7cd46c --- /dev/null +++ b/core/java/android/os/IExternalVibrationController.aidl @@ -0,0 +1,45 @@ +/* + * Copyright 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.os; + +/** + * {@hide} + */ + +interface IExternalVibrationController { + /** + * A method to ask a currently playing vibration to mute (i.e. not vibrate). + * + * This method is only valid from the time that + * {@link IExternalVibratorService#onExternalVibrationStart} returns until + * {@link IExternalVibratorService#onExternalVibrationStop} returns. + * + * @return whether the mute operation was successful + */ + boolean mute(); + + /** + * A method to ask a currently playing vibration to unmute (i.e. start vibrating). + * + * This method is only valid from the time that + * {@link IExternalVibratorService#onExternalVibrationStart} returns until + * {@link IExternalVibratorService#onExternalVibrationStop} returns. + * + * @return whether the unmute operation was successful + */ + boolean unmute(); +} diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl new file mode 100644 index 000000000000..666171fcbcb3 --- /dev/null +++ b/core/java/android/os/IExternalVibratorService.aidl @@ -0,0 +1,63 @@ +/** + * 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.os; + +import android.os.ExternalVibration; + +/** + * The communication channel by which an external system that wants to control the system + * vibrator can notify the vibrator subsystem. + * + * Some vibrators can be driven via multiple paths (e.g. as an audio channel) in addition to + * the usual interface, but we typically only want one vibration at a time playing because they + * don't mix well. In order to synchronize the two places where vibration might be controlled, + * we provide this interface so the vibrator subsystem has a chance to: + * + * 1) Decide whether the current vibration should play based on the current system policy. + * 2) Stop any currently on-going vibrations. + * {@hide} + */ +interface IExternalVibratorService { + const int SCALE_MUTE = -100; + const int SCALE_VERY_LOW = -2; + const int SCALE_LOW = -1; + const int SCALE_NONE = 0; + const int SCALE_HIGH = 1; + const int SCALE_VERY_HIGH = 2; + + /** + * A method called by the external system to start a vibration. + * + * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this + * returns any other scale level, then any currently playing vibration controlled by the + * requesting system must be muted and this vibration can begin playback. + * + * Note that the IExternalVibratorService implementation will not call mute on any currently + * playing external vibrations in order to avoid re-entrancy with the system on the other side. + * + * @param vibration An ExternalVibration + * + * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale + * level if it should. + */ + int onExternalVibrationStart(in ExternalVibration vib); + + /** + * A method called by the external system when a vibration no longer wants to play. + */ + void onExternalVibrationStop(in ExternalVibration vib); +} diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 74d434c51781..93d6f4c12128 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -119,6 +119,23 @@ interface IStatsManager { void removeDataFetchOperation(long configKey, in String packageName); /** + * Registers the given pending intent for this packagename. This intent is invoked when the + * active status of any of the configs sent by this package changes and will contain a list of + * config ids that are currently active. It also returns the list of configs that are currently + * active. There can be at most one active configs changed listener per package. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + long[] setActiveConfigsChangedOperation(in IBinder intentSender, in String packageName); + + /** + * Removes the active configs changed operation for the specified package name. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + void removeActiveConfigsChangedOperation(in String packageName); + + /** * Removes the configuration with the matching config key. No-op if this config key does not * exist. * diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 630bd2e509ff..d68eeeda2eb9 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -31,6 +31,7 @@ import static android.system.OsConstants.S_ISLNK; import static android.system.OsConstants.S_ISREG; import static android.system.OsConstants.S_IWOTH; +import android.annotation.TestApi; import android.content.BroadcastReceiver; import android.content.ContentProvider; import android.os.MessageQueue.OnFileDescriptorEventListener; @@ -580,6 +581,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * * @hide */ + @TestApi public static File getFile(FileDescriptor fd) throws IOException { try { final String path = Os.readlink("/proc/self/fd/" + fd.getInt$()); diff --git a/core/java/android/os/ParcelUuid.java b/core/java/android/os/ParcelUuid.java index 2c68ddd431ca..5b45ac231d70 100644 --- a/core/java/android/os/ParcelUuid.java +++ b/core/java/android/os/ParcelUuid.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.UnsupportedAppUsage; + import java.util.UUID; /** @@ -109,6 +111,7 @@ public final class ParcelUuid implements Parcelable { public static final Parcelable.Creator<ParcelUuid> CREATOR = new Parcelable.Creator<ParcelUuid>() { + @UnsupportedAppUsage public ParcelUuid createFromParcel(Parcel source) { long mostSigBits = source.readLong(); long leastSigBits = source.readLong(); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 4ce760f2c4a6..7f4254e29aaa 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -336,6 +336,13 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; /** + * User activity event type: {@link android.service.attention.AttentionService} taking action + * on behalf of user. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_ATTENTION = 4; + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java index 94441cae7567..a96618a92cc6 100644 --- a/core/java/android/os/SELinux.java +++ b/core/java/android/os/SELinux.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.UnsupportedAppUsage; import android.util.Slog; import java.io.File; @@ -69,6 +70,7 @@ public class SELinux { * @param path the pathname of the file object. * @return a security context given as a String. */ + @UnsupportedAppUsage public static final native String getFileContext(String path); /** diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java index 8d568c84b915..33795f81ded9 100644 --- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java +++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java @@ -78,15 +78,6 @@ public abstract class RuntimePermissionPresenterService extends Service { public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions( @NonNull String packageName); - /** - * Revokes the permission {@code permissionName} for app {@code packageName} - * - * @param packageName The package for which to revoke - * @param permissionName The permission to revoke - */ - public abstract void onRevokeRuntimePermission(@NonNull String packageName, - @NonNull String permissionName); - @Override public final IBinder onBind(Intent intent) { return new IRuntimePermissionPresenter.Stub() { @@ -99,17 +90,6 @@ public abstract class RuntimePermissionPresenterService extends Service { obtainMessage(RuntimePermissionPresenterService::getAppPermissions, RuntimePermissionPresenterService.this, packageName, callback)); } - - @Override - public void revokeRuntimePermission(String packageName, String permissionName) { - checkNotNull(packageName, "packageName"); - checkNotNull(permissionName, "permissionName"); - - mHandler.sendMessage( - obtainMessage(RuntimePermissionPresenterService::onRevokeRuntimePermission, - RuntimePermissionPresenterService.this, packageName, - permissionName)); - } }; } diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 8bd75d779154..8a52f1f0eec0 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -19,6 +19,7 @@ package android.provider; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AlarmManager; @@ -805,6 +806,7 @@ public final class CalendarContract { * * @hide */ + @TestApi public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { ACCOUNT_NAME, ACCOUNT_TYPE, @@ -1832,6 +1834,7 @@ public final class CalendarContract { * * @hide */ + @TestApi public static final String[] SYNC_WRITABLE_COLUMNS = new String[] { _SYNC_ID, DIRTY, diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 25554b937065..81e1eb99336b 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -126,6 +126,7 @@ public final class ContactsContract { * Prefix for column names that are not visible to client apps. * @hide */ + @TestApi public static final String HIDDEN_COLUMN_PREFIX = "x_"; /** @@ -8444,6 +8445,7 @@ public final class ContactsContract { * nothing will be done. * @hide */ + @TestApi public static final String UNDEMOTE_METHOD = "undemote"; /** diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 148dd910ef69..d58e00af5054 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -112,14 +112,12 @@ public final class DeviceConfig { @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"; + + /** If {@code true}, enables the attention features. */ + String PROPERTY_ATTENTION_ENABLED = "attention_enabled"; + + /** Settings for the attention features. */ + String PROPERTY_ATTENTION_SETTINGS = "attention_settings"; } /** @@ -182,6 +180,22 @@ public final class DeviceConfig { } /** + * Namespace for {@link AttentionManagerService} related features. + * + * @hide + */ + @SystemApi + public interface AttentionManagerService { + String NAMESPACE = "attention_manager_service"; + + /** If {@code true}, enables {@link AttentionManagerService} features. */ + String PROPERTY_SERVICE_ENABLED = "service_enabled"; + + /** Allows a CTS to inject a fake implementation. */ + String PROPERTY_COMPONENT_NAME = "component_name"; + } + + /** * Namespace for storage-related features. * * @hide diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f5c442f194ba..887175a80421 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -104,6 +104,11 @@ public final class MediaStore { */ public static final String VOLUME_EXTERNAL = "external"; + /** {@hide} */ @TestApi + public static final String SCAN_FILE_CALL = "scan_file"; + /** {@hide} */ @TestApi + public static final String SCAN_VOLUME_CALL = "scan_volume"; + /** * The method name used by the media scanner and mtp to tell the media provider to * rescan and reclassify that have become unhidden because of renaming folders or @@ -2992,6 +2997,7 @@ public final class MediaStore { * * @hide */ + @TestApi public static @NonNull File getVolumePath(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { @@ -3022,6 +3028,7 @@ public final class MediaStore { * * @hide */ + @TestApi public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5222fc36710f..927c729758b5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -486,6 +486,37 @@ public final class Settings { "android.settings.WIFI_IP_SETTINGS"; /** + * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start + * configuration. This intent should be used when you want to use this device to take on the + * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings + * will open a wifi selection screen for the user to indicate which network they would like + * to configure the device specified in the DPP URI string for and carry them through the rest + * of the flow for provisioning the device. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard + * against this by checking WifiManager.isEasyConnectSupported(); + * <p> + * Input: + * The following keys in the bundle with their associated value. + * <ul> + * <li>"qrCode": Standard Easy Connect (Wi-Fi DPP) URI bootstrapping information as a + * string.</li> + * </ul> + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE = + "android.settings.PROCESS_WIFI_EASY_CONNECT_QR_CODE"; + + /** + * An extra to put in the bundle for {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE} intents. + * It must contain properly formatted Easy Connect (Wi-Fi DPP) URI bootstrapping information for + * the process to proceed. + */ + public static final String EXTRA_QR_CODE = "android.provider.extra.QR_CODE"; + + /** * Activity Action: Show settings to allow configuration of data and view data usage. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -8786,6 +8817,7 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED); + CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD); if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD); CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); @@ -11722,6 +11754,7 @@ public final class Settings { * entity_list_default (String[]) * entity_list_not_editable (String[]) * entity_list_editable (String[]) + * lang_id_threshold_override (float) * </pre> * * <p> @@ -11766,6 +11799,44 @@ public final class Settings { public static final String SYNC_MANAGER_CONSTANTS = "sync_manager_constants"; /** + * Broadcast dispatch tuning parameters specific to foreground broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true" + * + * The following keys are supported: + * <pre> + * bcast_timeout (long) + * bcast_slow_time (long) + * bcast_deferral (long) + * bcast_deferral_decay_factor (float) + * bcast_deferral_floor (long) + * </pre> + * + * @hide + */ + public static final String BROADCAST_FG_CONSTANTS = "bcast_fg_constants"; + + /** + * Broadcast dispatch tuning parameters specific to background broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true". + * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys. + * + * @hide + */ + public static final String BROADCAST_BG_CONSTANTS = "bcast_bg_constants"; + + /** + * Broadcast dispatch tuning parameters specific to specific "offline" broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true". + * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys. + * + * @hide + */ + public static final String BROADCAST_OFFLOAD_CONSTANTS = "bcast_offload_constants"; + + /** * Whether or not App Standby feature is enabled by system. This controls throttling of apps * based on usage patterns and predictions. Platform will turn on this feature if both this * flag and {@link #ADAPTIVE_BATTERY_MANAGEMENT_ENABLED} is on. @@ -14280,6 +14351,17 @@ public final class Settings { */ public static final String APPOP_HISTORY_PARAMETERS = "appop_history_parameters"; + + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * This is used as an override for constants defined in BatteryStatsImpl for + * ease of experimentation. + * + * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS + * @hide + */ + public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY = + "battery_charging_state_update_delay"; } /** diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 140336e93357..dce5d56e24a4 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -18,6 +18,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -289,6 +290,7 @@ public class VoicemailContract { * Path to the media content file. Internal only field. * @hide */ + @TestApi public static final String _DATA = "_data"; // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index b6788f578bd6..93189b310485 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -63,15 +63,17 @@ public final class Adjustment implements Parcelable { /** * Data type: ArrayList of {@link android.app.Notification.Action}. - * Used to suggest extra actions for a notification. + * Used to suggest contextual actions for a notification. + * + * @see Notification.Action.Builder#setContextual(boolean) */ - public static final String KEY_SMART_ACTIONS = "key_smart_actions"; + public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions"; /** * Data type: ArrayList of {@link CharSequence}. * Used to suggest smart replies for a notification. */ - public static final String KEY_SMART_REPLIES = "key_smart_replies"; + public static final String KEY_TEXT_REPLIES = "key_text_replies"; /** * Data type: int, one of importance values e.g. diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 4052ed782671..db9351b030df 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -52,7 +52,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_mobile_network_v2", "true"); DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); - DEFAULT_FLAGS.put("settings_slice_injection", "false"); + DEFAULT_FLAGS.put("settings_slice_injection", "true"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put("settings_wifi_dpp", "true"); DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true"); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index f58efc900427..e3a6bd7a6949 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -26,6 +26,7 @@ import android.app.KeyguardManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.ColorSpace; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -953,6 +954,24 @@ public final class Display { } /** + * Returns the preferred wide color space of the Display. + * The returned wide gamut color space is based on hardware capability and + * is preferred by the composition pipeline. + * Returns null if the display doesn't support wide color gamut. + * {@link Display#isWideColorGamut()}. + */ + @Nullable + public ColorSpace getPreferredWideGamutColorSpace() { + synchronized (this) { + updateDisplayInfoLocked(); + if (mDisplayInfo.isWideColorGamut()) { + return mGlobal.getPreferredWideGamutColorSpace(); + } + return null; + } + } + + /** * Gets the supported color modes of this device. * @hide */ diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java new file mode 100644 index 000000000000..7026d2b1389c --- /dev/null +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -0,0 +1,157 @@ +/* + * 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; + +import static android.view.InsetsState.TYPE_IME; + +import android.os.Parcel; +import android.text.TextUtils; +import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets.Type; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Controls the visibility and animations of IME window insets source. + * @hide + */ +public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { + private EditorInfo mFocusedEditor; + private EditorInfo mPreRenderedEditor; + /** + * Determines if IME would be shown next time IME is pre-rendered for currently focused + * editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}. + */ + private boolean mShowOnNextImeRender; + private boolean mHasWindowFocus; + + public ImeInsetsSourceConsumer( + InsetsState state, Supplier<Transaction> transactionSupplier, + InsetsController controller) { + super(TYPE_IME, state, transactionSupplier, controller); + } + + public void onPreRendered(EditorInfo info) { + mPreRenderedEditor = info; + if (mShowOnNextImeRender) { + mShowOnNextImeRender = false; + if (isServedEditorRendered()) { + applyImeVisibility(true /* setVisible */); + } + } + } + + public void onServedEditorChanged(EditorInfo info) { + if (isDummyOrEmptyEditor(info)) { + mShowOnNextImeRender = false; + } + mFocusedEditor = info; + } + + public void applyImeVisibility(boolean setVisible) { + if (!mHasWindowFocus) { + // App window doesn't have focus, any visibility changes would be no-op. + return; + } + + if (setVisible) { + mController.show(Type.IME); + } else { + mController.hide(Type.IME); + } + } + + @Override + public void onWindowFocusGained() { + mHasWindowFocus = true; + getImm().registerImeConsumer(this); + } + + @Override + public void onWindowFocusLost() { + mHasWindowFocus = false; + } + + private boolean isDummyOrEmptyEditor(EditorInfo info) { + // TODO(b/123044812): Handle dummy input gracefully in IME Insets API + return info == null || (info.fieldId <= 0 && info.inputType <= 0); + } + + private boolean isServedEditorRendered() { + if (mFocusedEditor == null || mPreRenderedEditor == null + || isDummyOrEmptyEditor(mFocusedEditor) + || isDummyOrEmptyEditor(mPreRenderedEditor)) { + // No view is focused or ready. + return false; + } + return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor); + } + + @VisibleForTesting + public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) { + // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change + // IME views. + boolean areOptionsSimilar = + info1.imeOptions == info2.imeOptions + && info1.inputType == info2.inputType + && TextUtils.equals(info1.packageName, info2.packageName); + areOptionsSimilar &= info1.privateImeOptions != null + ? info1.privateImeOptions.equals(info2.privateImeOptions) : true; + + if (!areOptionsSimilar) { + return false; + } + + // compare bundle extras. + if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) { + return true; + } + if ((info1.extras == null && info2.extras != null) + || (info1.extras == null && info2.extras != null)) { + return false; + } + if (info1.extras.hashCode() == info2.extras.hashCode() + || info1.extras.equals(info1)) { + return true; + } + if (info1.extras.size() != info2.extras.size()) { + return false; + } + if (info1.extras.toString().equals(info2.extras.toString())) { + return true; + } + + // Compare bytes + Parcel parcel1 = Parcel.obtain(); + info1.extras.writeToParcel(parcel1, 0); + parcel1.setDataPosition(0); + Parcel parcel2 = Parcel.obtain(); + info2.extras.writeToParcel(parcel2, 0); + parcel2.setDataPosition(0); + + return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); + } + + private InputMethodManager getImm() { + return mController.getViewRoot().mDisplayContext.getSystemService(InputMethodManager.class); + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 2142c36f8803..4f9ecd575326 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -262,7 +264,7 @@ public class InsetsController implements WindowInsetsController { if (controller != null) { return controller; } - controller = new InsetsSourceConsumer(type, mState, Transaction::new, this); + controller = createConsumerOfType(type); mSourceConsumers.put(type, controller); return controller; } @@ -274,6 +276,32 @@ public class InsetsController implements WindowInsetsController { } /** + * Called when current window gains focus. + */ + public void onWindowFocusGained() { + getSourceConsumer(TYPE_IME).onWindowFocusGained(); + } + + /** + * Called when current window loses focus. + */ + public void onWindowFocusLost() { + getSourceConsumer(TYPE_IME).onWindowFocusLost(); + } + + ViewRootImpl getViewRoot() { + return mViewRoot; + } + + private InsetsSourceConsumer createConsumerOfType(int type) { + if (type == TYPE_IME) { + return new ImeInsetsSourceConsumer(mState, Transaction::new, this); + } else { + return new InsetsSourceConsumer(type, mState, Transaction::new, this); + } + } + + /** * Sends the local visibility state back to window manager. */ private void sendStateToWindowManager() { diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 7937cb69b80e..f48318cd7d0a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -30,12 +30,12 @@ import java.util.function.Supplier; */ public class InsetsSourceConsumer { + protected final InsetsController mController; + protected boolean mVisible; private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetType int mType; private final InsetsState mState; - private final InsetsController mController; private @Nullable InsetsSourceControl mSourceControl; - private boolean mVisible; public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -43,7 +43,7 @@ public class InsetsSourceConsumer { mState = state; mTransactionSupplier = transactionSupplier; mController = controller; - mVisible = InsetsState.getDefaultVisibly(type); + mVisible = InsetsState.getDefaultVisibility(type); } public void setControl(@Nullable InsetsSourceControl control) { @@ -76,6 +76,16 @@ public class InsetsSourceConsumer { setVisible(false); } + /** + * Called when current window gains focus + */ + public void onWindowFocusGained() {} + + /** + * Called when current window loses focus. + */ + public void onWindowFocusLost() {} + boolean applyLocalVisibilityOverride() { // If we don't have control, we are not able to change the visibility. diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index a6af1a296faf..4f809fe6d54a 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -278,7 +278,7 @@ public class InsetsState implements Parcelable { } } - public static boolean getDefaultVisibly(@InsetType int type) { + public static boolean getDefaultVisibility(@InsetType int type) { switch (type) { case TYPE_TOP_BAR: case TYPE_SIDE_BAR_1: diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 863b717008d2..4032a6b84801 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -46,10 +46,9 @@ import android.hardware.display.DisplayedContentSamplingAttributes; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import android.view.Surface.OutOfResourcesException; @@ -60,6 +59,7 @@ import dalvik.system.CloseGuard; import libcore.util.NativeAllocationRegistry; import java.io.Closeable; +import java.nio.ByteBuffer; /** * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is @@ -75,7 +75,7 @@ public final class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, - int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid) + int w, int h, int format, int flags, long parentObject, Parcel metadata) throws OutOfResourcesException; private static native long nativeReadFromParcel(Parcel in); private static native long nativeCopyFromSurfaceControl(long nativeObject); @@ -182,6 +182,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken, IBinder toToken); private static native boolean nativeGetProtectedContentSupport(); + private static native void nativeSetMetadata(long transactionObj, int key, Parcel data); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -413,6 +414,24 @@ public final class SurfaceControl implements Parcelable { } /** + * owner UID. + * @hide + */ + public static final int METADATA_OWNER_UID = 1; + + /** + * Window type as per {@link WindowManager.LayoutParams}. + * @hide + */ + public static final int METADATA_WINDOW_TYPE = 2; + + /** + * Task id to allow association between surfaces and task. + * @hide + */ + public static final int METADATA_TASK_ID = 3; + + /** * Builder class for {@link SurfaceControl} objects. */ public static class Builder { @@ -423,8 +442,7 @@ public final class SurfaceControl implements Parcelable { private int mFormat = PixelFormat.OPAQUE; private String mName; private SurfaceControl mParent; - private int mWindowType = -1; - private int mOwnerUid = -1; + private SparseIntArray mMetadata; /** * Begin building a SurfaceControl with a given {@link SurfaceSession}. @@ -455,8 +473,8 @@ public final class SurfaceControl implements Parcelable { throw new IllegalArgumentException( "Only buffer layers can set a valid buffer size."); } - return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat, - mFlags, mParent, mWindowType, mOwnerUid); + return new SurfaceControl( + mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata); } /** @@ -581,23 +599,17 @@ public final class SurfaceControl implements Parcelable { } /** - * Set surface metadata. + * Sets a metadata int. * - * Currently these are window-types as per {@link WindowManager.LayoutParams} and - * owner UIDs. Child surfaces inherit their parents - * metadata so only the WindowManager needs to set this on root Surfaces. - * - * @param windowType A window-type - * @param ownerUid UID of the window owner. + * @param key metadata key + * @param data associated data * @hide */ - public Builder setMetadata(int windowType, int ownerUid) { - if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) { - throw new UnsupportedOperationException( - "It only makes sense to set Surface metadata from the WindowManager"); + public Builder setMetadata(int key, int data) { + if (mMetadata == null) { + mMetadata = new SparseIntArray(); } - mWindowType = windowType; - mOwnerUid = ownerUid; + mMetadata.put(key, data); return this; } @@ -682,13 +694,12 @@ public final class SurfaceControl implements Parcelable { * @param h The surface initial height. * @param flags The surface creation flags. Should always include {@link #HIDDEN} * in the creation flags. - * @param windowType The type of the window as specified in WindowManager.java. - * @param ownerUid A unique per-app ID. + * @param metadata Initial metadata. * * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, - SurfaceControl parent, int windowType, int ownerUid) + SurfaceControl parent, SparseIntArray metadata) throws OutOfResourcesException, IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name must not be null"); @@ -706,8 +717,21 @@ public final class SurfaceControl implements Parcelable { mName = name; mWidth = w; mHeight = h; - mNativeObject = nativeCreate(session, name, w, h, format, flags, - parent != null ? parent.mNativeObject : 0, windowType, ownerUid); + Parcel metaParcel = Parcel.obtain(); + try { + if (metadata != null && metadata.size() > 0) { + metaParcel.writeInt(metadata.size()); + for (int i = 0; i < metadata.size(); ++i) { + metaParcel.writeInt(metadata.keyAt(i)); + metaParcel.writeByteArray( + ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array()); + } + } + mNativeObject = nativeCreate(session, name, w, h, format, flags, + parent != null ? parent.mNativeObject : 0, metaParcel); + } finally { + metaParcel.recycle(); + } if (mNativeObject == 0) { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); @@ -2326,6 +2350,30 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets an arbitrary piece of metadata on the surface. This is a helper for int data. + * @hide + */ + public Transaction setMetadata(int key, int data) { + Parcel parcel = Parcel.obtain(); + parcel.writeInt(data); + try { + setMetadata(key, parcel); + } finally { + parcel.recycle(); + } + return this; + } + + /** + * Sets an arbitrary piece of metadata on the surface. + * @hide + */ + public Transaction setMetadata(int key, Parcel data) { + nativeSetMetadata(mNativeObject, key, data); + return this; + } + + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. * diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 45e6c50d9ada..ecbec652fcf0 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -492,7 +492,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb if (mBackgroundControl == null) { return; } - if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) { + if ((mSurfaceFlags & PixelFormat.OPAQUE) != 0) { mBackgroundControl.show(); mBackgroundControl.setLayer(Integer.MIN_VALUE); } else { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a031b704627f..f47eb10efd29 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2802,6 +2802,11 @@ public final class ViewRootImpl implements ViewParent, hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } + if (hasWindowFocus) { + mInsetsController.onWindowFocusGained(); + } else { + mInsetsController.onWindowFocusLost(); + } if (mAdded) { profileRendering(hasWindowFocus); diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index b9017b365f90..d7f1b9f2c3e1 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -156,8 +156,7 @@ public final class ContentCaptureManager { // Wait for system server to return the component name. final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); mHandler.sendMessage(obtainMessage( - ContentCaptureManager::handleReceiverServiceComponentName, - this, mContext.getUserId(), resultReceiver)); + ContentCaptureManager::handleGetComponentName, this, resultReceiver)); try { return resultReceiver.getParcelableResult(); @@ -198,7 +197,7 @@ public final class ContentCaptureManager { Preconditions.checkNotNull(request); try { - mService.removeUserData(mContext.getUserId(), request); + mService.removeUserData(request); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -227,9 +226,9 @@ public final class ContentCaptureManager { /** Retrieves the component name of the target content capture service through system_server. */ - private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) { + private void handleGetComponentName(@NonNull IResultReceiver resultReceiver) { try { - mService.getReceiverServiceComponentName(userId, resultReceiver); + mService.getServiceComponentName(resultReceiver); } catch (RemoteException e) { Log.w(TAG, "Unable to retrieve service component name: " + e); } diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 51aea162f1db..56ed8bfab9ad 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -34,21 +34,21 @@ import java.util.List; */ oneway interface IContentCaptureManager { /** - * Starts a new session for the provided {@code userId} running as part of the + * Starts a new session for the calling user running as part of the * app's activity identified by {@code activityToken}/{@code componentName}. * * @param sessionId Unique session id as provided by the app. * @param flags Meta flags that enable or disable content capture (see * {@link IContentCaptureContext#flags}). */ - void startSession(int userId, IBinder activityToken, in ComponentName componentName, + void startSession(IBinder activityToken, in ComponentName componentName, String sessionId, int flags, in IResultReceiver result); /** - * Marks the end of a session for the provided {@code userId} identified by + * Marks the end of a session for the calling user identified by * the corresponding {@code startSession}'s {@code sessionId}. */ - void finishSession(int userId, String sessionId); + void finishSession(String sessionId); /** * Returns the content capture service's component name (if enabled and @@ -56,10 +56,10 @@ oneway interface IContentCaptureManager { * @param Receiver of the content capture service's @{code ComponentName} * provided {@code Bundle} with key "{@code EXTRA}". */ - void getReceiverServiceComponentName(int userId, in IResultReceiver result); + void getServiceComponentName(in IResultReceiver result); /** - * Requests the removal of user data for the provided {@code userId}. + * Requests the removal of user data for the calling user. */ - void removeUserData(int userId, in UserDataRemovalRequest request); + void removeUserData(in UserDataRemovalRequest request); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 9e99c88da3ca..83dbf2d5bb58 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -211,8 +211,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { try { if (mSystemServerInterface == null) return; - mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken, - componentName, mId, flags, new IResultReceiver.Stub() { + mSystemServerInterface.startSession(mApplicationToken, componentName, mId, flags, + new IResultReceiver.Stub() { @Override public void send(int resultCode, Bundle resultData) { IBinder binder = null; @@ -473,7 +473,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { try { if (mSystemServerInterface == null) return; - mSystemServerInterface.finishSession(mContext.getUserId(), mId); + mSystemServerInterface.finishSession(mId); } catch (RemoteException e) { Log.e(TAG, "Error destroying system-service session " + mId + " for " + getDebugState() + ": " + e); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0cb1800996c9..7fee3ef29a09 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -55,6 +55,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; import android.view.Display; +import android.view.ImeInsetsSourceConsumer; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; @@ -441,6 +442,13 @@ public final class InputMethodManager { */ private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE; + /** + * When {@link ViewRootImpl#sNewInsetsMode} is set to + * >= {@link ViewRootImpl#NEW_INSETS_MODE_IME}, {@link ImeInsetsSourceConsumer} applies the + * IME visibility and listens for other state changes. + */ + private ImeInsetsSourceConsumer mImeInsetsConsumer; + final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20); final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); @@ -454,6 +462,8 @@ public final class InputMethodManager { static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; static final int MSG_REPORT_FULLSCREEN_MODE = 10; + static final int MSG_REPORT_PRE_RENDERED = 15; + static final int MSG_APPLY_IME_VISIBILITY = 20; private static boolean isAutofillUIShowing(View servedView) { AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class); @@ -650,6 +660,23 @@ public final class InputMethodManager { } return; } + case MSG_REPORT_PRE_RENDERED: { + synchronized (mH) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.onPreRendered((EditorInfo) msg.obj); + } + } + return; + + } + case MSG_APPLY_IME_VISIBILITY: { + synchronized (mH) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0); + } + } + return; + } } } } @@ -729,6 +756,18 @@ public final class InputMethodManager { .sendToTarget(); } + @Override + public void reportPreRendered(EditorInfo info) { + mH.obtainMessage(MSG_REPORT_PRE_RENDERED, 0, 0, info) + .sendToTarget(); + } + + @Override + public void applyImeVisibility(boolean setVisible) { + mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0) + .sendToTarget(); + } + }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); @@ -1515,6 +1554,7 @@ public final class InputMethodManager { // Hook 'em up and let 'er rip. mCurrentTextBoxAttribute = tba; + maybeCallServedViewChangedLocked(tba); mServedConnecting = false; if (mServedInputConnectionWrapper != null) { mServedInputConnectionWrapper.deactivate(); @@ -1730,6 +1770,10 @@ public final class InputMethodManager { mCurrentTextBoxAttribute = null; mCompletions = null; mServedConnecting = true; + // servedView has changed and it's not editable. + if (!mServedView.onCheckIsTextEditor()) { + maybeCallServedViewChangedLocked(null); + } } if (ic != null) { @@ -1828,6 +1872,21 @@ public final class InputMethodManager { } /** + * Register for IME state callbacks and applying visibility in + * {@link android.view.ImeInsetsSourceConsumer}. + * @hide + */ + public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) { + if (imeInsetsConsumer == null) { + throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null."); + } + + synchronized (mH) { + mImeInsetsConsumer = imeInsetsConsumer; + } + } + + /** * Report the current selection range. * * <p><strong>Editor authors</strong>, you need to call this method whenever @@ -2705,6 +2764,12 @@ public final class InputMethodManager { } } + private void maybeCallServedViewChangedLocked(EditorInfo tba) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.onServedEditorChanged(tba); + } + } + void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java index 57ed7f923e88..7c79d44e7d31 100644 --- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java +++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java @@ -93,8 +93,14 @@ public class InputMethodSystemProperty { * {@code true} when per-profile IME is enabled. * @hide */ - public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED - || Build.IS_DEBUGGABLE && SystemProperties.getBoolean( - PROP_DEBUG_PER_PROFILE_IME, false); - + public static final boolean PER_PROFILE_IME_ENABLED; + static { + if (MULTI_CLIENT_IME_ENABLED) { + PER_PROFILE_IME_ENABLED = true; + } else if (Build.IS_DEBUGGABLE) { + PER_PROFILE_IME_ENABLED = SystemProperties.getBoolean(PROP_DEBUG_PER_PROFILE_IME, true); + } else { + PER_PROFILE_IME_ENABLED = true; + } + } } diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index ce680ecbd119..7f928f74da19 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -46,6 +46,7 @@ import java.util.StringJoiner; * entity_list_default (String[]) * entity_list_not_editable (String[]) * entity_list_editable (String[]) + * lang_id_threshold_override (float) * </pre> * * <p> @@ -94,6 +95,8 @@ public final class TextClassificationConstants { "in_app_conversation_action_types_default"; private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT = "notification_conversation_action_types_default"; + private static final String LANG_ID_THRESHOLD_OVERRIDE = + "lang_id_threshold_override"; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -106,8 +109,8 @@ public final class TextClassificationConstants { private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000; private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000; private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100; - private static final String ENTITY_LIST_DELIMITER = ":"; - private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(ENTITY_LIST_DELIMITER) + private static final String STRING_LIST_DELIMITER = ":"; + private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(STRING_LIST_DELIMITER) .add(TextClassifier.TYPE_ADDRESS) .add(TextClassifier.TYPE_EMAIL) .add(TextClassifier.TYPE_PHONE) @@ -116,7 +119,7 @@ public final class TextClassificationConstants { .add(TextClassifier.TYPE_DATE_TIME) .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString(); private static final String CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES = - new StringJoiner(ENTITY_LIST_DELIMITER) + new StringJoiner(STRING_LIST_DELIMITER) .add(ConversationAction.TYPE_TEXT_REPLY) .add(ConversationAction.TYPE_CREATE_REMINDER) .add(ConversationAction.TYPE_CALL_PHONE) @@ -127,6 +130,13 @@ public final class TextClassificationConstants { .add(ConversationAction.TYPE_VIEW_CALENDAR) .add(ConversationAction.TYPE_VIEW_MAP) .toString(); + /** + * < 0 : Not set. Use value from LangId model. + * 0 - 1: Override value in LangId model. + * > 1 : Effectively turns off the foreign language detection. Scores should never be > 1. + * @see EntityConfidence + */ + private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f; private final boolean mSystemTextClassifierEnabled; private final boolean mLocalTextClassifierEnabled; @@ -144,6 +154,7 @@ public final class TextClassificationConstants { private final List<String> mEntityListEditable; private final List<String> mInAppConversationActionTypesDefault; private final List<String> mNotificationConversationActionTypesDefault; + private final float mLangIdThresholdOverride; private TextClassificationConstants(@Nullable String settings) { final KeyValueListParser parser = new KeyValueListParser(','); @@ -186,21 +197,24 @@ public final class TextClassificationConstants { mGenerateLinksLogSampleRate = parser.getInt( GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT); - mEntityListDefault = parseEntityList(parser.getString( + mEntityListDefault = parseStringList(parser.getString( ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE)); - mEntityListNotEditable = parseEntityList(parser.getString( + mEntityListNotEditable = parseStringList(parser.getString( ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE)); - mEntityListEditable = parseEntityList(parser.getString( + mEntityListEditable = parseStringList(parser.getString( ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE)); - mInAppConversationActionTypesDefault = parseEntityList(parser.getString( + mInAppConversationActionTypesDefault = parseStringList(parser.getString( IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES)); - mNotificationConversationActionTypesDefault = parseEntityList(parser.getString( + mNotificationConversationActionTypesDefault = parseStringList(parser.getString( NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES)); + mLangIdThresholdOverride = parser.getFloat( + LANG_ID_THRESHOLD_OVERRIDE, + LANG_ID_THRESHOLD_OVERRIDE_DEFAULT); } /** Load from a settings string. */ @@ -272,8 +286,12 @@ public final class TextClassificationConstants { return mNotificationConversationActionTypesDefault; } - private static List<String> parseEntityList(String listStr) { - return Collections.unmodifiableList(Arrays.asList(listStr.split(ENTITY_LIST_DELIMITER))); + public float getLangIdThresholdOverride() { + return mLangIdThresholdOverride; + } + + private static List<String> parseStringList(String listStr) { + return Collections.unmodifiableList(Arrays.asList(listStr.split(STRING_LIST_DELIMITER))); } void dump(IndentingPrintWriter pw) { @@ -296,6 +314,7 @@ public final class TextClassificationConstants { pw.printPair("getInAppConversationActionTypes", mInAppConversationActionTypesDefault); pw.printPair("getNotificationConversationActionTypes", mNotificationConversationActionTypesDefault); + pw.printPair("getLangIdThresholdOverride", mLangIdThresholdOverride); pw.decreaseIndent(); pw.println(); } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index a5b7c621be38..7782079213e7 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -567,8 +567,9 @@ public final class TextClassifierImpl implements TextClassifier { } } - // TODO: Make this configurable. - final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f; + final float foreignTextThreshold = mSettings.getLangIdThresholdOverride() >= 0 + ? mSettings.getLangIdThresholdOverride() + : 0.5f /* TODO: Load this from the langId model. */; boolean isPrimaryAction = true; final ArrayList<Intent> sourceIntents = new ArrayList<>(); for (LabeledIntent labeledIntent : IntentFactory.create( @@ -602,6 +603,10 @@ public final class TextClassifierImpl implements TextClassifier { } private boolean isForeignText(String text, float threshold) { + if (threshold > 1) { + return false; + } + // TODO: Revisit this algorithm. try { final LangIdModel.LanguageResult[] langResults = getLangIdImpl().detectLanguages(text); diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index b1609fcfe56a..735c3eba226e 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -89,9 +89,10 @@ public final class TextLanguage implements Parcelable { /** * Returns the language locale at the specified index. Locales are ordered from high * confidence to low confidence. + * <p> + * See {@link #getLocaleHypothesisCount()} for the number of locales available. * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getLocaleHypothesisCount() for the number of locales available. */ @NonNull public ULocale getLocale(int index) { @@ -109,7 +110,8 @@ public final class TextLanguage implements Parcelable { } /** - * Returns a bundle containing non-structured extra information about this result. + * Returns a bundle containing non-structured extra information about this result. What is + * returned in the extras is specific to the {@link TextClassifier} implementation. * * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should prefer * to hold a reference to the returned bundle rather than frequently calling this method. diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index afe467012307..5147306d277c 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -359,7 +359,7 @@ public final class Magnifier { /** * @return the initial width of the content magnified and copied to the magnifier, in pixels * @see Magnifier.Builder#setSize(int, int) - * @see Magnifier.Builder#setZoom(float) + * @see Magnifier.Builder#setInitialZoom(float) */ @Px public int getSourceWidth() { @@ -369,7 +369,7 @@ public final class Magnifier { /** * @return the initial height of the content magnified and copied to the magnifier, in pixels * @see Magnifier.Builder#setSize(int, int) - * @see Magnifier.Builder#setZoom(float) + * @see Magnifier.Builder#setInitialZoom(float) */ @Px public int getSourceHeight() { @@ -394,7 +394,7 @@ public final class Magnifier { * If the zoom is x and the magnifier window size is (width, height), the original size * of the content being magnified will be (width / x, height / x). * @return the zoom applied to the content - * @see Magnifier.Builder#setZoom(float) + * @see Magnifier.Builder#setInitialZoom(float) */ public float getZoom() { return mZoom; @@ -1196,10 +1196,12 @@ public final class Magnifier { * (content_width * zoom, content_height * zoom), which will coincide with the size * of the magnifier. A zoom of 1 will translate to no magnification (the content will * be just copied to the magnifier with no scaling). The zoom defaults to 1.25. + * Note that the zoom can also be changed after the instance is built, using the + * {@link Magnifier#setZoom(float)} method. * @param zoom the zoom to be set */ @NonNull - public Builder setZoom(@FloatRange(from = 0f) float zoom) { + public Builder setInitialZoom(@FloatRange(from = 0f) float zoom) { Preconditions.checkArgumentPositive(zoom, "Zoom should be positive"); mZoom = zoom; return this; diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 42acb09d50d6..299801a1b8fc 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -18,6 +18,12 @@ package com.android.internal.app; import android.app.Activity; import android.app.ActivityManager; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -98,6 +104,21 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; + + /** + * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true, + * {@link AppPredictionManager} will be queried for direct share targets. + */ + // TODO(b/123089490): Replace with system flag + private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false; + // TODO(b/123088566) Share these in a better way. + private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; + public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share"; + private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; + public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; + private AppPredictor mAppPredictor; + private AppPredictor.Callback mAppPredictorCallback; + /** * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of * binding to every ChooserTargetService implementation. @@ -309,6 +330,35 @@ public class ChooserActivity extends ResolverActivity { mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost); + + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + final IntentFilter filter = getTargetIntentFilter(); + Bundle extras = new Bundle(); + extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter); + AppPredictionManager appPredictionManager = + getSystemService(AppPredictionManager.class); + mAppPredictor = appPredictionManager.createAppPredictionSession( + new AppPredictionContext.Builder(this) + .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT) + .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) + .setExtras(extras) + .build()); + mAppPredictorCallback = resultList -> { + final List<DisplayResolveInfo> driList = + getDisplayResolveInfos(mChooserListAdapter); + final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos = + new ArrayList<>(); + for (AppTarget appTarget : resultList) { + shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo( + appTarget.getShortcutInfo(), + new ComponentName( + appTarget.getPackageName(), appTarget.getClassName()))); + } + sendShareShortcutInfoList(shareShortcutInfos, driList); + }; + mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback); + } + if (DEBUG) { Log.d(TAG, "System Time Cost is " + systemCost); } @@ -339,6 +389,10 @@ public class ChooserActivity extends ResolverActivity { } unbindRemainingServices(); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback); + mAppPredictor.destroy(); + } } @Override @@ -606,15 +660,10 @@ public class ChooserActivity extends ResolverActivity { } } - private void queryDirectShareTargets(ChooserListAdapter adapter) { - final IntentFilter filter = getTargetIntentFilter(); - if (filter == null) { - return; - } - + private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) { // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo // and use the old code path. This Ugliness should go away when Sharesheet is refactored. - final List<DisplayResolveInfo> driList = new ArrayList<>(); + List<DisplayResolveInfo> driList = new ArrayList<>(); int targetsToQuery = 0; for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) { final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); @@ -634,40 +683,57 @@ public class ChooserActivity extends ResolverActivity { break; } } + return driList; + } + + private void queryDirectShareTargets(ChooserListAdapter adapter) { + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.requestPredictionUpdate(); + return; + } + final IntentFilter filter = getTargetIntentFilter(); + if (filter == null) { + return; + } + final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter); AsyncTask.execute(() -> { ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter); + sendShareShortcutInfoList(resultList, driList); + }); + } - // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path - // for direct share targets. After ShareSheet is refactored we should use the - // ShareShortcutInfos directly. - boolean resultMessageSent = false; - for (int i = 0; i < driList.size(); i++) { - List<ChooserTarget> chooserTargets = new ArrayList<>(); - for (int j = 0; j < resultList.size(); j++) { - if (driList.get(i).getResolvedComponentName().equals( + private void sendShareShortcutInfoList( + List<ShortcutManager.ShareShortcutInfo> resultList, + List<DisplayResolveInfo> driList) { + // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path + // for direct share targets. After ShareSheet is refactored we should use the + // ShareShortcutInfos directly. + boolean resultMessageSent = false; + for (int i = 0; i < driList.size(); i++) { + List<ChooserTarget> chooserTargets = new ArrayList<>(); + for (int j = 0; j < resultList.size(); j++) { + if (driList.get(i).getResolvedComponentName().equals( resultList.get(j).getTargetComponent())) { - chooserTargets.add(convertToChooserTarget(resultList.get(j))); - } + chooserTargets.add(convertToChooserTarget(resultList.get(j))); } - if (chooserTargets.isEmpty()) { - continue; - } - - final Message msg = Message.obtain(); - msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; - msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); - mChooserHandler.sendMessage(msg); - resultMessageSent = true; } - - if (resultMessageSent) { - final Message msg = Message.obtain(); - msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; - mChooserHandler.sendMessage(msg); + if (chooserTargets.isEmpty()) { + continue; } - }); + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; + msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); + mChooserHandler.sendMessage(msg); + resultMessageSent = true; + } + + if (resultMessageSent) { + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; + mChooserHandler.sendMessage(msg); + } } private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) { @@ -726,6 +792,9 @@ public class ChooserActivity extends ResolverActivity { void updateModelAndChooserCounts(TargetInfo info) { if (info != null) { + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + sendClickToAppPredictor(info); + } final ResolveInfo ri = info.getResolveInfo(); Intent targetIntent = getTargetIntent(); if (ri != null && ri.activityInfo != null && targetIntent != null) { @@ -738,13 +807,39 @@ public class ChooserActivity extends ResolverActivity { Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName); Log.d(TAG, "Action to be updated is " + targetIntent.getAction()); } - } else if(DEBUG) { + } else if (DEBUG) { Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo"); } } mIsSuccessfullySelected = true; } + private void sendClickToAppPredictor(TargetInfo targetInfo) { + if (!(targetInfo instanceof ChooserTargetInfo)) { + return; + } + ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget(); + ComponentName componentName = chooserTarget.getComponentName(); + Bundle extras = chooserTarget.getIntentExtras(); + if (extras == null) { + return; + } + String shortcutId = extras.getString(Intent.EXTRA_SHORTCUT_ID); + if (shortcutId == null) { + return; + } + mAppPredictor.notifyAppTargetEvent( + new AppTargetEvent.Builder( + new AppTarget( + new AppTargetId(shortcutId), + componentName.getPackageName(), + componentName.getClassName(), + getUser()), + AppTargetEvent.ACTION_LAUNCH + ).setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE) + .build()); + } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { if (mRefinementResultReceiver != null) { mRefinementResultReceiver.destroy(); @@ -1064,6 +1159,10 @@ public class ChooserActivity extends ResolverActivity { return mBadgeContentDescription; } + public ChooserTarget getChooserTarget() { + return mChooserTarget; + } + @Override public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { return new ChooserTargetInfo(this, fillInIntent, flags); diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 514ff76372a9..d7514d1fe26c 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -154,4 +154,7 @@ interface IBatteryStats { oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info); oneway void noteModemControllerActivity(in ModemActivityInfo info); oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info); + + /** {@hide} */ + boolean setChargingStateUpdateDelayMillis(int delay); } diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index d0272e08b53a..e27ff0076054 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -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. @@ -17,6 +17,7 @@ package com.android.internal.inputmethod; import android.net.Uri; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputContentUriToken; @@ -39,4 +40,6 @@ interface IInputMethodPrivilegedOperations { boolean switchToNextInputMethod(boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(); void notifyUserAction(); + void reportPreRendered(in EditorInfo info); + void applyImeVisibility(boolean setVisible); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 8978496073e5..d42c607b98bf 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; @@ -347,4 +348,40 @@ public final class InputMethodPrivilegedOperations { throw e.rethrowFromSystemServer(); } } + + /** + * Calls {@link IInputMethodPrivilegedOperations#reportPreRendered(info)}. + * + * @param info {@link EditorInfo} of the currently rendered {@link TextView}. + */ + @AnyThread + public void reportPreRendered(EditorInfo info) { + final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); + if (ops == null) { + return; + } + try { + ops.reportPreRendered(info); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}. + * + * @param setVisible {@code true} to set IME visible, else hidden. + */ + @AnyThread + public void applyImeVisibility(boolean setVisible) { + final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); + if (ops == null) { + return; + } + try { + ops.applyImeVisibility(setVisible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 534361e13c7d..c6afee24cfb5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13395,11 +13395,22 @@ public class BatteryStatsImpl extends BatteryStats { mResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS), false /* notifyForDescendants */, this); + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY), + false /* notifyForDescendants */, this); updateConstants(); } @Override public void onChange(boolean selfChange, Uri uri) { + if (uri.equals( + Settings.Global.getUriFor( + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) { + synchronized (BatteryStatsImpl.this) { + updateBatteryChargedDelayMsLocked(); + } + return; + } updateConstants(); } @@ -13443,12 +13454,21 @@ public class BatteryStatsImpl extends BatteryStats { DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; - BATTERY_CHARGED_DELAY_MS = mParser.getInt( - KEY_BATTERY_CHARGED_DELAY_MS, - DEFAULT_BATTERY_CHARGED_DELAY_MS); + updateBatteryChargedDelayMsLocked(); } } + private void updateBatteryChargedDelayMsLocked() { + // a negative value indicates that we should ignore this override + final int delay = Settings.Global.getInt(mResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + -1); + + BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt( + KEY_BATTERY_CHARGED_DELAY_MS, + DEFAULT_BATTERY_CHARGED_DELAY_MS); + } + private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) { TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled; if (isEnabled && !wasEnabled) { diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 6a28059d3fd0..943c726b173e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -160,4 +160,9 @@ oneway interface IStatusBar void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); + + /** + * Notifies System UI that the display is ready to show system decorations. + */ + void onDisplayReady(int displayId); } diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index e5ad1f47d37d..7398e9526a42 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -1354,6 +1355,7 @@ public class StateMachine { * Add a new state to the state machine, parent will be null * @param state to add */ + @UnsupportedAppUsage public final void addState(State state) { mSmHandler.addState(state, null); } @@ -1372,6 +1374,7 @@ public class StateMachine { * * @param initialState is the state which will receive the first message. */ + @UnsupportedAppUsage public final void setInitialState(State initialState) { mSmHandler.setInitialState(initialState); } @@ -1410,6 +1413,7 @@ public class StateMachine { * * @param destState will be the state that receives the next message. */ + @UnsupportedAppUsage public final void transitionTo(IState destState) { mSmHandler.transitionTo(destState); } @@ -2053,6 +2057,7 @@ public class StateMachine { /** * Start the state machine. */ + @UnsupportedAppUsage public void start() { // mSmHandler can be null if the state machine has quit. SmHandler smh = mSmHandler; diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 17b2bc46de36..2cfdaaa5ada6 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -16,6 +16,8 @@ package com.android.internal.view; +import android.view.inputmethod.EditorInfo; + import com.android.internal.view.InputBindResult; /** @@ -27,4 +29,6 @@ oneway interface IInputMethodClient { void onUnbindMethod(int sequence, int unbindReason); void setActive(boolean active, boolean fullscreen); void reportFullscreenMode(boolean fullscreen); + void reportPreRendered(in EditorInfo info); + void applyImeVisibility(boolean setVisible); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index be127009a21d..9afd5d0bff03 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -166,6 +166,7 @@ cc_library_shared { "android_media_AudioRecord.cpp", "android_media_AudioSystem.cpp", "android_media_AudioTrack.cpp", + "android_media_AudioAttributes.cpp", "android_media_DeviceCallback.cpp", "android_media_JetPlayer.cpp", "android_media_MediaMetricsJNI.cpp", @@ -275,6 +276,7 @@ cc_library_shared { "libselinux", "libandroidicu", "libmedia", + "libmedia_helper", "libmediametrics", "libmeminfo", "libaudioclient", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f458299468b1..c90071a0de44 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -107,6 +107,7 @@ extern int register_android_media_AudioEffectDescriptor(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); extern int register_android_media_AudioSystem(JNIEnv *env); extern int register_android_media_AudioTrack(JNIEnv *env); +extern int register_android_media_AudioAttributes(JNIEnv *env); extern int register_android_media_MicrophoneInfo(JNIEnv *env); extern int register_android_media_JetPlayer(JNIEnv *env); extern int register_android_media_ToneGenerator(JNIEnv *env); @@ -1470,6 +1471,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_AudioSystem), REG_JNI(register_android_media_AudioRecord), REG_JNI(register_android_media_AudioTrack), + REG_JNI(register_android_media_AudioAttributes), REG_JNI(register_android_media_JetPlayer), REG_JNI(register_android_media_MicrophoneInfo), REG_JNI(register_android_media_RemoteDisplay), diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index dd7633ac6269..cc22ff02e338 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -29,6 +29,7 @@ #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" #include "SkFont.h" +#include "SkFontMetrics.h" #include "SkFontTypes.h" #include "SkMaskFilter.h" #include "SkPath.h" @@ -668,12 +669,12 @@ namespace PaintGlue { } static jint getHinting(jlong paintHandle) { - return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getHinting() + return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getSkFont().getHinting() == kNo_SkFontHinting ? 0 : 1; } static void setHinting(jlong paintHandle, jint mode) { - reinterpret_cast<Paint*>(paintHandle)->setHinting( + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setHinting( mode == 0 ? kNo_SkFontHinting : kNormal_SkFontHinting); } diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp index 8174a4184c83..2d1fec819e97 100644 --- a/core/jni/android_hardware_HardwareBuffer.cpp +++ b/core/jni/android_hardware_HardwareBuffer.cpp @@ -104,6 +104,21 @@ static jlong android_hardware_HardwareBuffer_getNativeFinalizer(JNIEnv* env, job return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyWrapper)); } +static jboolean android_hardware_HardwareBuffer_isSupported(JNIEnv* env, jobject clazz, + jint width, jint height, jint format, jint layers, jlong usage) { + + AHardwareBuffer_Desc desc; + desc.width = width; + desc.height = height; + desc.format = format; + desc.layers = layers; + desc.usage = usage; + desc.stride = 0; + desc.rfu0 = 0; + desc.rfu1 = 0; + return AHardwareBuffer_isSupported(&desc); +} + //---------------------------------------------------------------------------- // Accessors // ---------------------------------------------------------------------------- @@ -234,6 +249,8 @@ static const JNINativeMethod gMethods[] = { (void*) android_hardware_HardwareBuffer_write }, { "nReadHardwareBufferFromParcel", "(Landroid/os/Parcel;)J", (void*) android_hardware_HardwareBuffer_read }, + { "nIsSupported", "(IIIIJ)Z", + (void*) android_hardware_HardwareBuffer_isSupported }, // --------------- @FastNative ---------------------- { "nGetWidth", "(J)I", (void*) android_hardware_HardwareBuffer_getWidth }, diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp new file mode 100644 index 000000000000..bfe61626c1a2 --- /dev/null +++ b/core/jni/android_media_AudioAttributes.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioAttributes-JNI" + +#include <inttypes.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include "core_jni_helpers.h" + +#include <utils/Log.h> +#include <vector> + +#include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedUtfChars.h> + +#include "android_media_AudioAttributes.h" +#include "android_media_AudioErrors.h" + +// ---------------------------------------------------------------------------- + +using namespace android; + +// ---------------------------------------------------------------------------- +static const char* const kClassPathName = "android/media/AudioAttributes"; + +static jclass gAudioAttributesClass; +static struct { + jfieldID mUsage; // AudioAttributes.mUsage + jfieldID mSource; // AudioAttributes.mSource + jfieldID mContentType; // AudioAttributes.mContentType + jfieldID mFlags; // AudioAttributes.mFlags + jfieldID mFormattedTags; // AudioAttributes.mFormattedTags +} gAudioAttributesFields; + +static jclass gAudioAttributesBuilderClass; +static jmethodID gAudioAttributesBuilderCstor; +static struct { + jmethodID build; + jmethodID setUsage; + jmethodID setInternalCapturePreset; + jmethodID setContentType; + jmethodID setFlags; + jmethodID addTag; +} gAudioAttributesBuilderMethods; + + +static jint nativeAudioAttributesFromJavaAudioAttributes( + JNIEnv* env, jobject jAudioAttributes, audio_attributes_t *aa) +{ + if (env == nullptr) { + return AUDIO_JAVA_DEAD_OBJECT; + } + if (jAudioAttributes == nullptr) { + ALOGE("Invalid AudioAttributes java object"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioAttributes, gAudioAttributesClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + const jstring jtags = + (jstring) env->GetObjectField(jAudioAttributes, gAudioAttributesFields.mFormattedTags); + if (jtags == nullptr) { + return AUDIO_JAVA_NO_INIT; + } + const char* tags = env->GetStringUTFChars(jtags, NULL); + // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it + strncpy(aa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1); + env->ReleaseStringUTFChars(jtags, tags); + + // Record ? + aa->source = (audio_source_t) env->GetIntField(jAudioAttributes, + gAudioAttributesFields.mSource); + // Track ? + aa->usage = (audio_usage_t) env->GetIntField(jAudioAttributes, gAudioAttributesFields.mUsage); + + aa->content_type = + (audio_content_type_t) env->GetIntField(jAudioAttributes, + gAudioAttributesFields.mContentType); + + aa->flags = (audio_flags_mask_t)env->GetIntField(jAudioAttributes, + gAudioAttributesFields.mFlags); + + ALOGV("AudioAttributes for usage=%d content=%d source=%d tags=%s flags=%08x tags=%s", + aa->usage, aa->content_type, aa->source, aa->tags, aa->flags, aa->tags); + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint nativeAudioAttributesToJavaAudioAttributes( + JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes) +{ + ScopedLocalRef<jobject> jAttributeBuilder(env, env->NewObject(gAudioAttributesBuilderClass, + gAudioAttributesBuilderCstor)); + if (jAttributeBuilder.get() == nullptr) { + return (jint)AUDIO_JAVA_ERROR; + } + env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.setUsage, + attributes.usage); + env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.setInternalCapturePreset, + attributes.source); + env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.setContentType, + attributes.content_type); + env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.setFlags, + attributes.flags); + env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.addTag, + env->NewStringUTF(attributes.tags)); + + *jAudioAttributes = env->CallObjectMethod(jAttributeBuilder.get(), + gAudioAttributesBuilderMethods.build); + return (jint)AUDIO_JAVA_SUCCESS; +} + +// ---------------------------------------------------------------------------- +JNIAudioAttributeHelper::UniqueAaPtr JNIAudioAttributeHelper::makeUnique() +{ + audio_attributes_t *aa = new (calloc(1, sizeof(audio_attributes_t))) + audio_attributes_t{AUDIO_ATTRIBUTES_INITIALIZER}; + return UniqueAaPtr{aa, free}; +} + +jint JNIAudioAttributeHelper::nativeFromJava(JNIEnv* env, jobject jAudioAttributes, + audio_attributes_t *paa) +{ + return nativeAudioAttributesFromJavaAudioAttributes(env, jAudioAttributes, paa); +} + +jint JNIAudioAttributeHelper::nativeToJava( + JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes) +{ + return nativeAudioAttributesToJavaAudioAttributes(env, jAudioAttributes, attributes); +} + +jint JNIAudioAttributeHelper::getJavaArray( + JNIEnv* env, jobjectArray *jAudioAttributeArray, jint numAudioAttributes) +{ + *jAudioAttributeArray = env->NewObjectArray(numAudioAttributes, gAudioAttributesClass, NULL); + return jAudioAttributeArray == NULL? (jint)AUDIO_JAVA_ERROR : (jint)AUDIO_JAVA_SUCCESS; +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + // n/a +}; + +int register_android_media_AudioAttributes(JNIEnv *env) +{ + jclass audioAttributesClass = FindClassOrDie(env, kClassPathName); + gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass); + gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I"); + gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I"); + gAudioAttributesFields.mContentType = + GetFieldIDOrDie(env, audioAttributesClass, "mContentType", "I"); + gAudioAttributesFields.mFlags = GetFieldIDOrDie(env, audioAttributesClass, "mFlags", "I"); + gAudioAttributesFields.mFormattedTags = + GetFieldIDOrDie(env, audioAttributesClass, "mFormattedTags", "Ljava/lang/String;"); + + jclass audioAttributesBuilderClass = FindClassOrDie( + env, "android/media/AudioAttributes$Builder"); + gAudioAttributesBuilderClass = MakeGlobalRefOrDie(env, audioAttributesBuilderClass); + gAudioAttributesBuilderCstor = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "<init>", "()V"); + gAudioAttributesBuilderMethods.build = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "build", "()Landroid/media/AudioAttributes;"); + gAudioAttributesBuilderMethods.setUsage = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "setUsage", + "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "setInternalCapturePreset", + "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "setContentType", + "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.setFlags = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "setFlags", + "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie( + env, audioAttributesBuilderClass, "addTag", + "(Ljava/lang/String;)Landroid/media/AudioAttributes$Builder;"); + + env->DeleteLocalRef(audioAttributesClass); + + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} diff --git a/core/jni/android_media_AudioAttributes.h b/core/jni/android_media_AudioAttributes.h new file mode 100644 index 000000000000..c55835222086 --- /dev/null +++ b/core/jni/android_media_AudioAttributes.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#pragma once + +#include "jni.h" + +#include <memory.h> + +#include <system/audio.h> + +namespace android { + +class JNIAudioAttributeHelper +{ +public: + using UniqueAaPtr = std::unique_ptr<audio_attributes_t, decltype(free)*>; + + /** + * @brief makeUnique helper to prevent leak + * @return a unique ptr of 0-initialized native audio attributes structure + */ + static UniqueAaPtr makeUnique(); + + /** + * @brief nativeFromJava Gets the underlying AudioAttributes from an AudioAttributes Java + * object. + * @param env + * @param jAudioAttributes JAVA AudioAttribute object + * @param paa native AudioAttribute pointer + * @return AUDIO_JAVA_SUCCESS on success, error code otherwise + */ + static jint nativeFromJava( + JNIEnv* env, jobject jAudioAttributes, audio_attributes_t *attributes); + + /** + * @brief nativeToJava AudioAttributes Java object from a native AudioAttributes. + * @param env + * @param jAudioAttributes JAVA AudioAttribute object + * @param attributes native AudioAttribute + * @return AUDIO_JAVA_SUCCESS on success, error code otherwise + */ + static jint nativeToJava( + JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes); + + /** + * @brief getJavaArray: creates an array of JAVA AudioAttributes objects + * @param env + * @param jAudioAttributeArray + * @param numAudioAttributes + * @return Array of AudioAttributes objects + */ + static jint getJavaArray( + JNIEnv* env, jobjectArray *jAudioAttributeArray, jint numAudioAttributes); +}; + +}; // namespace android diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 67c306413bb2..02dffdc2ca25 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -35,6 +35,7 @@ #include "android_media_DeviceCallback.h" #include "android_media_MediaMetricsJNI.h" #include "android_media_MicrophoneInfo.h" +#include "android_media_AudioAttributes.h" // ---------------------------------------------------------------------------- @@ -42,7 +43,6 @@ using namespace android; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioRecord"; -static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes"; static jclass gArrayListClass; static struct { @@ -56,12 +56,6 @@ struct audio_record_fields_t { jfieldID nativeCallbackCookie; // provides access to the AudioRecord callback data jfieldID nativeDeviceCallback; // provides access to the JNIDeviceCallback instance }; -struct audio_attributes_fields_t { - jfieldID fieldRecSource; // AudioAttributes.mSource - jfieldID fieldFlags; // AudioAttributes.mFlags - jfieldID fieldFormattedTags;// AudioAttributes.mFormattedTags -}; -static audio_attributes_fields_t javaAudioAttrFields; static audio_record_fields_t javaAudioRecordFields; static struct { jfieldID fieldFramePosition; // AudioTimestamp.framePosition @@ -213,7 +207,6 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); nSession = NULL; - audio_attributes_t *paa = NULL; sp<AudioRecord> lpRecorder = 0; audiorecord_callback_cookie *lpCallbackData = NULL; @@ -275,15 +268,11 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, lpRecorder = new AudioRecord(String16(opPackageNameStr.c_str())); // read the AudioAttributes values - paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t)); - const jstring jtags = - (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags); - const char* tags = env->GetStringUTFChars(jtags, NULL); - // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it - strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1); - env->ReleaseStringUTFChars(jtags, tags); - paa->source = (audio_source_t) env->GetIntField(jaa, javaAudioAttrFields.fieldRecSource); - paa->flags = (audio_flags_mask_t)env->GetIntField(jaa, javaAudioAttrFields.fieldFlags); + auto paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags); audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE; @@ -311,7 +300,7 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, AudioRecord::TRANSFER_DEFAULT, flags, -1, -1, // default uid, pid - paa); + paa.get()); if (status != NO_ERROR) { ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.", @@ -368,19 +357,10 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, // of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize() env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, (jlong)lpCallbackData); - if (paa != NULL) { - // audio attributes were copied in AudioRecord creation - free(paa); - paa = NULL; - } - return (jint) AUDIO_JAVA_SUCCESS; // failure: native_init_failure: - if (paa != NULL) { - free(paa); - } env->DeleteGlobalRef(lpCallbackData->audioRecord_class); env->DeleteGlobalRef(lpCallbackData->audioRecord_ref); delete lpCallbackData; @@ -972,13 +952,6 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env, audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J"); - // Get the AudioAttributes class and fields - jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName); - javaAudioAttrFields.fieldRecSource = GetFieldIDOrDie(env, audioAttrClass, "mSource", "I"); - javaAudioAttrFields.fieldFlags = GetFieldIDOrDie(env, audioAttrClass, "mFlags", "I"); - javaAudioAttrFields.fieldFormattedTags = GetFieldIDOrDie(env, - audioAttrClass, "mFormattedTags", "Ljava/lang/String;"); - // Get the RecordTimestamp class and fields jclass audioTimestampClass = FindClassOrDie(env, "android/media/AudioTimestamp"); javaAudioTimestampFields.fieldFramePosition = diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 0d8ede794b73..34abcd889644 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -36,6 +36,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_MicrophoneInfo.h" +#include "android_media_AudioAttributes.h" // ---------------------------------------------------------------------------- @@ -153,15 +154,6 @@ static struct { jfieldID mRule; } gAudioMixMatchCriterionFields; -static jclass gAudioAttributesClass; -static struct { - jfieldID mUsage; - jfieldID mSource; - jfieldID mContentType; - jfieldID mFlags; - jfieldID mFormattedTags; -} gAudioAttributesFields; - static const char* const kEventHandlerClassPathName = "android/media/AudioPortEventHandler"; static struct { @@ -705,27 +697,6 @@ static void convertAudioGainConfigToNative(JNIEnv *env, env->DeleteLocalRef(jValues); } -static jint convertAudioAttributesToNative(JNIEnv *env, - audio_attributes_t *nAudioAttributes, - const jobject jAudioAttributes) -{ - nAudioAttributes->usage = (audio_usage_t)env->GetIntField(jAudioAttributes, - gAudioAttributesFields.mUsage); - nAudioAttributes->source = (audio_source_t)env->GetIntField(jAudioAttributes, - gAudioAttributesFields.mSource); - nAudioAttributes->content_type = (audio_content_type_t)env->GetIntField(jAudioAttributes, - gAudioAttributesFields.mContentType); - nAudioAttributes->flags = env->GetIntField(jAudioAttributes, - gAudioAttributesFields.mFlags); - const jstring jtags = (jstring)env->GetObjectField(jAudioAttributes, - gAudioAttributesFields.mFormattedTags); - const char *tags = env->GetStringUTFChars(jtags, NULL); - strncpy(nAudioAttributes->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1); - env->ReleaseStringUTFChars(jtags, tags); - return (jint)AUDIO_JAVA_SUCCESS; -} - - static jint convertAudioPortConfigToNative(JNIEnv *env, struct audio_port_config *nAudioPortConfig, const jobject jAudioPortConfig, @@ -1705,22 +1676,19 @@ android_media_AudioSystem_startAudioSource(JNIEnv *env, jobject clazz, if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) { return AUDIO_JAVA_BAD_VALUE; } - if (!env->IsInstanceOf(jAudioAttributes, gAudioAttributesClass)) { - return AUDIO_JAVA_BAD_VALUE; - } struct audio_port_config nAudioPortConfig = {}; jint jStatus = convertAudioPortConfigToNativeWithDevicePort(env, &nAudioPortConfig, jAudioPortConfig, false); if (jStatus != AUDIO_JAVA_SUCCESS) { return jStatus; } - audio_attributes_t nAudioAttributes = {}; - jStatus = convertAudioAttributesToNative(env, &nAudioAttributes, jAudioAttributes); - if (jStatus != AUDIO_JAVA_SUCCESS) { + auto paa = JNIAudioAttributeHelper::makeUnique(); + jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { return jStatus; } audio_port_handle_t handle; - status_t status = AudioSystem::startAudioSource(&nAudioPortConfig, &nAudioAttributes, &handle); + status_t status = AudioSystem::startAudioSource(&nAudioPortConfig, paa.get(), &handle); ALOGV("AudioSystem::startAudioSource() returned %d handle %d", status, handle); return handle > 0 ? handle : nativeToJavaStatus(status); } @@ -1833,12 +1801,16 @@ static jint convertAudioMixToNative(JNIEnv *env, case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: { jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr); + + auto paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) { - nCriterion.mValue.mUsage = (audio_usage_t)env->GetIntField(jAttributes, - gAudioAttributesFields.mUsage); + nCriterion.mValue.mUsage = paa->usage; } else { - nCriterion.mValue.mSource = (audio_source_t)env->GetIntField(jAttributes, - gAudioAttributesFields.mSource); + nCriterion.mValue.mSource = paa->source; } env->DeleteLocalRef(jAttributes); } @@ -2395,17 +2367,6 @@ int register_android_media_AudioSystem(JNIEnv *env) "I"); gAudioMixMatchCriterionFields.mRule = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mRule", "I"); - - jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes"); - gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass); - gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I"); - gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I"); - gAudioAttributesFields.mContentType = GetFieldIDOrDie(env, - audioAttributesClass, "mContentType", "I"); - gAudioAttributesFields.mFlags = GetFieldIDOrDie(env, audioAttributesClass, "mFlags", "I"); - gAudioAttributesFields.mFormattedTags = GetFieldIDOrDie(env, - audioAttributesClass, "mFormattedTags", "Ljava/lang/String;"); - // AudioTrackRoutingProxy methods gClsAudioTrackRoutingProxy = android::FindClassOrDie(env, "android/media/AudioTrackRoutingProxy"); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 1065738466e7..f9f28dabc694 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -23,6 +23,7 @@ #include "core_jni_helpers.h" #include <utils/Log.h> +#include <media/AudioParameter.h> #include <media/AudioSystem.h> #include <media/AudioTrack.h> @@ -36,6 +37,7 @@ #include "android_media_PlaybackParams.h" #include "android_media_DeviceCallback.h" #include "android_media_VolumeShaper.h" +#include "android_media_AudioAttributes.h" #include <cinttypes> @@ -47,7 +49,6 @@ using ::android::media::VolumeShaper; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioTrack"; -static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes"; struct audio_track_fields_t { // these fields provide access from C++ to the... @@ -56,14 +57,7 @@ struct audio_track_fields_t { jfieldID jniData; // stores in Java additional resources used by the native AudioTrack jfieldID fieldStreamType; // ... mStreamType field in the AudioTrack Java object }; -struct audio_attributes_fields_t { - jfieldID fieldUsage; // AudioAttributes.mUsage - jfieldID fieldContentType; // AudioAttributes.mContentType - jfieldID fieldFlags; // AudioAttributes.mFlags - jfieldID fieldFormattedTags;// AudioAttributes.mFormattedTags -}; static audio_track_fields_t javaAudioTrackFields; -static audio_attributes_fields_t javaAudioAttrFields; static PlaybackParams::fields_t gPlaybackParamsFields; static VolumeShaperHelper::fields_t gVolumeShaperFields; @@ -248,8 +242,6 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job AudioTrackJniStorage* lpJniStorage = NULL; - audio_attributes_t *paa = NULL; - jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { ALOGE("Can't find %s when setting up callback.", kClassPathName); @@ -303,18 +295,11 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job lpTrack = new AudioTrack(); // read the AudioAttributes values - paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t)); - const jstring jtags = - (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags); - const char* tags = env->GetStringUTFChars(jtags, NULL); - // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it - strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1); - env->ReleaseStringUTFChars(jtags, tags); - paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage); - paa->content_type = - (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType); - paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags); - + auto paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s", paa->usage, paa->content_type, paa->flags, paa->tags); @@ -356,7 +341,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC, offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values - paa); + paa.get()); break; @@ -383,7 +368,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job AudioTrack::TRANSFER_SHARED, NULL, // default offloadInfo -1, -1, // default uid, pid values - paa); + paa.get()); break; default: @@ -451,20 +436,11 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job // since we had audio attributes, the stream type was derived from them during the // creation of the native AudioTrack: push the same value to the Java object env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType()); - if (paa != NULL) { - // audio attributes were copied in AudioTrack creation - free(paa); - paa = NULL; - } - return (jint) AUDIO_JAVA_SUCCESS; // failures: native_init_failure: - if (paa != NULL) { - free(paa); - } if (nSession != NULL) { env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); } @@ -1311,6 +1287,33 @@ static jint android_media_AudioTrack_get_port_id(JNIEnv *env, jobject thiz) { } // ---------------------------------------------------------------------------- +static void android_media_AudioTrack_set_delay_padding(JNIEnv *env, jobject thiz, + jint delayInFrames, jint paddingInFrames) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "AudioTrack not initialized"); + return; + } + AudioParameter param = AudioParameter(); + param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), (int) delayInFrames); + param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), (int) paddingInFrames); + lpTrack->setParameters(param.toString()); +} + +static void android_media_AudioTrack_set_eos(JNIEnv *env, jobject thiz) { + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "AudioTrack not initialized"); + return; + } + AudioParameter param = AudioParameter(); + param.addInt(String8("EOS"), 1); + lpTrack->setParameters(param.toString()); +} + +// ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { // name, signature, funcPtr @@ -1385,6 +1388,8 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioTrack_get_volume_shaper_state}, {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id}, + {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding}, + {"native_set_eos", "()V", (void *)android_media_AudioTrack_set_eos}, }; @@ -1440,17 +1445,6 @@ int register_android_media_AudioTrack(JNIEnv *env) env->DeleteLocalRef(audioTrackClass); - // Get the AudioAttributes class and fields - jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName); - javaAudioAttrFields.fieldUsage = GetFieldIDOrDie(env, audioAttrClass, "mUsage", "I"); - javaAudioAttrFields.fieldContentType = GetFieldIDOrDie(env, - audioAttrClass, "mContentType", "I"); - javaAudioAttrFields.fieldFlags = GetFieldIDOrDie(env, audioAttrClass, "mFlags", "I"); - javaAudioAttrFields.fieldFormattedTags = GetFieldIDOrDie(env, - audioAttrClass, "mFormattedTags", "Ljava/lang/String;"); - - env->DeleteLocalRef(audioAttrClass); - // initialize PlaybackParams field info gPlaybackParamsFields.init(env); diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp index 38f7a7e25389..3204317cab68 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.cpp +++ b/core/jni/android_media_MediaMetricsJNI.cpp @@ -1,90 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <android_runtime/AndroidRuntime.h> -#include <jni.h> -#include <nativehelper/JNIHelp.h> - -#include "android_media_MediaMetricsJNI.h" -#include <media/MediaAnalyticsItem.h> - - -namespace android { - -// place the attributes into a java PersistableBundle object -jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) { - - jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); - if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); - return NULL; - } - // sometimes the caller provides one for us to fill - if (mybundle == NULL) { - // create the bundle - jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); - mybundle = env->NewObject(clazzBundle, constructID); - if (mybundle == NULL) { - return NULL; - } - } - - // grab methods that we can invoke - jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); - jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); - jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); - jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); - - // env, class, method, {parms} - //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint); - - // iterate through my attributes - // -- get name, get type, get value - // -- insert appropriately into the bundle - for (size_t i = 0 ; i < item->mPropCount; i++ ) { - MediaAnalyticsItem::Prop *prop = &item->mProps[i]; - // build the key parameter from prop->mName - jstring keyName = env->NewStringUTF(prop->mName); - // invoke the appropriate method to insert - switch (prop->mType) { - case MediaAnalyticsItem::kTypeInt32: - env->CallVoidMethod(mybundle, setIntID, - keyName, (jint) prop->u.int32Value); - break; - case MediaAnalyticsItem::kTypeInt64: - env->CallVoidMethod(mybundle, setLongID, - keyName, (jlong) prop->u.int64Value); - break; - case MediaAnalyticsItem::kTypeDouble: - env->CallVoidMethod(mybundle, setDoubleID, - keyName, (jdouble) prop->u.doubleValue); - break; - case MediaAnalyticsItem::kTypeCString: - env->CallVoidMethod(mybundle, setStringID, keyName, - env->NewStringUTF(prop->u.CStringValue)); - break; - default: - ALOGE("to_String bad item type: %d for %s", - prop->mType, prop->mName); - break; - } - } - - return mybundle; -} - -}; // namespace android - +../../media/jni/android_media_MediaMetricsJNI.cpp
\ No newline at end of file diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h index b3cb4d293399..c7a685beb7e5 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.h +++ b/core/jni/android_media_MediaMetricsJNI.h @@ -1,33 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ -#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ - -#include <jni.h> -#include <nativehelper/JNIHelp.h> -#include <media/MediaAnalyticsItem.h> - -namespace android { - -class MediaMetricsJNI { -public: - static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); -}; - -}; // namespace android - -#endif // _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ +../../media/jni/android_media_MediaMetricsJNI.h
\ No newline at end of file diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp index 6f975b23e5bd..d25192a57228 100644 --- a/core/jni/android_util_FileObserver.cpp +++ b/core/jni/android_util_FileObserver.cpp @@ -16,6 +16,8 @@ */ #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" @@ -98,31 +100,45 @@ static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd #endif } -static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask) +static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, + jobjectArray pathStrings, jint mask, + jintArray wfdArray) { - int res = -1; + ScopedIntArrayRW wfds(env, wfdArray); + if (wfds.get() == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRW"); + } #if defined(__linux__) if (fd >= 0) { - const char* path = env->GetStringUTFChars(pathString, NULL); + size_t count = wfds.size(); + for (jsize i = 0; i < count; ++i) { + jstring pathString = (jstring) env->GetObjectArrayElement(pathStrings, i); - res = inotify_add_watch(fd, path, mask); + ScopedUtfChars path(env, pathString); - env->ReleaseStringUTFChars(pathString, path); + wfds[i] = inotify_add_watch(fd, path.c_str(), mask); + } } #endif - - return res; } -static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd) +static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, + jint fd, jintArray wfdArray) { #if defined(__linux__) - inotify_rm_watch((int)fd, (uint32_t)wfd); + ScopedIntArrayRO wfds(env, wfdArray); + if (wfds.get() == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRO"); + } + size_t count = wfds.size(); + for (size_t i = 0; i < count; ++i) { + inotify_rm_watch((int)fd, (uint32_t)wfds[i]); + } #endif } @@ -131,8 +147,8 @@ static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ { "init", "()I", (void*)android_os_fileobserver_init }, { "observe", "(I)V", (void*)android_os_fileobserver_observe }, - { "startWatching", "(ILjava/lang/String;I)I", (void*)android_os_fileobserver_startWatching }, - { "stopWatching", "(II)V", (void*)android_os_fileobserver_stopWatching } + { "startWatching", "(I[Ljava/lang/String;I[I)V", (void*)android_os_fileobserver_startWatching }, + { "stopWatching", "(I[I)V", (void*)android_os_fileobserver_stopWatching } }; diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index b8139a712fd7..f9407f43af9f 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -131,6 +131,7 @@ int android_view_Surface_mapPublicFormatToHalFormat(PublicFormat f) { switch(f) { case PublicFormat::JPEG: case PublicFormat::DEPTH_POINT_CLOUD: + case PublicFormat::DEPTH_JPEG: return HAL_PIXEL_FORMAT_BLOB; case PublicFormat::DEPTH16: return HAL_PIXEL_FORMAT_Y16; @@ -161,6 +162,8 @@ android_dataspace android_view_Surface_mapPublicFormatToHalDataspace( case PublicFormat::NV21: case PublicFormat::YV12: return HAL_DATASPACE_V0_JFIF; + case PublicFormat::DEPTH_JPEG: + return static_cast<android_dataspace> (HAL_DATASPACE_DYNAMIC_DEPTH); default: // Most formats map to UNKNOWN return HAL_DATASPACE_UNKNOWN; @@ -223,8 +226,12 @@ PublicFormat android_view_Surface_mapHalFormatDataspaceToPublicFormat( case HAL_DATASPACE_V0_JFIF: return PublicFormat::JPEG; default: - // Assume otherwise-marked blobs are also JPEG - return PublicFormat::JPEG; + if (dataSpace == static_cast<android_dataspace>(HAL_DATASPACE_DYNAMIC_DEPTH)) { + return PublicFormat::DEPTH_JPEG; + } else { + // Assume otherwise-marked blobs are also JPEG + return PublicFormat::JPEG; + } } break; case HAL_PIXEL_FORMAT_BGRA_8888: diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 69877c7d3930..f1b259e10cf5 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -124,7 +124,7 @@ static jlong nativeGetNativeTransactionFinalizer(JNIEnv* env, jclass clazz) { static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, - jint windowType, jint ownerUid) { + jobject metadataParcel) { ScopedUtfChars name(env, nameStr); sp<SurfaceComposerClient> client; if (sessionObj != NULL) { @@ -134,8 +134,18 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, } SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface; + LayerMetadata metadata; + Parcel* parcel = parcelForJavaObject(env, metadataParcel); + if (parcel && !parcel->objectsCount()) { + status_t err = metadata.readFromParcel(parcel); + if (err != NO_ERROR) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Metadata parcel has wrong format"); + } + } + status_t err = client->createSurfaceChecked( - String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid); + String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata)); if (err == NAME_NOT_FOUND) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return 0; @@ -377,6 +387,28 @@ static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactio transaction->transferTouchFocus(fromToken, toToken); } +static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint id, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (!parcel) { + jniThrowNullPointerException(env, "attribute data"); + return; + } + if (parcel->objectsCount()) { + jniThrowException(env, "java/lang/RuntimeException", + "Tried to marshall a Parcel that contained Binder objects."); + return; + } + + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + std::vector<uint8_t> byteData(parcel->dataSize()); + memcpy(byteData.data(), parcel->data(), parcel->dataSize()); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setMetadata(ctrl, id, std::move(byteData)); +} + static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jfloatArray fColor) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -981,7 +1013,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz, // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { - {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J", + {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", (void*)nativeCreate }, {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel }, @@ -1099,6 +1131,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetInputWindowInfo }, {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V", (void*)nativeTransferTouchFocus }, + {"nativeSetMetadata", "(JILandroid/os/Parcel;)V", + (void*)nativeSetMetadata }, {"nativeGetDisplayedContentSamplingAttributes", "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;", (void*)nativeGetDisplayedContentSamplingAttributes }, diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp index 50b2288a614a..76b0fc167264 100644 --- a/core/jni/com_android_internal_os_AtomicDirectory.cpp +++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp @@ -29,7 +29,7 @@ static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env, return -1; } int fd; - if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) { + if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC))) == -1) { ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno)); return -1; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 8681d4b3f42e..6ee960668a3e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -821,7 +821,7 @@ static bool NeedsNoRandomizeWorkaround() { // Utility to close down the Zygote socket file descriptors while // the child is still running as root with Zygote's privileges. Each -// descriptor (if any) is closed via dup2(), replacing it with a valid +// descriptor (if any) is closed via dup3(), replacing it with a valid // (open) descriptor to /dev/null. static void DetachDescriptors(JNIEnv* env, @@ -829,15 +829,15 @@ static void DetachDescriptors(JNIEnv* env, fail_fn_t fail_fn) { if (fds_to_close.size() > 0) { - android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR)); + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC)); if (devnull_fd == -1) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } for (int fd : fds_to_close) { ALOGV("Switching descriptor %d to /dev/null", fd); - if (dup2(devnull_fd, fd) == -1) { - fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno))); + if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) { + fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno))); } } } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 4e486630adae..4b37f13cbb33 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -423,13 +423,13 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { } void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { - const int dev_null_fd = open("/dev/null", O_RDWR); + const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); if (dev_null_fd < 0) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - if (dup2(dev_null_fd, fd) == -1) { - fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", + if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) { + fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s", fd, strerror(errno))); } diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h index 36487b3b40b3..984e942207c1 100644 --- a/core/jni/include/android_runtime/android_view_Surface.h +++ b/core/jni/include/android_runtime/android_view_Surface.h @@ -57,7 +57,8 @@ enum class PublicFormat { YV12 = 0x32315659, Y8 = 0x20203859, // @hide Y16 = 0x20363159, // @hide - DEPTH16 = 0x44363159 + DEPTH16 = 0x44363159, + DEPTH_JPEG = 0x69656963, }; /* Gets the underlying ANativeWindow for a Surface. */ diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto index bba832880669..f702b3e37bda 100644 --- a/core/proto/android/app/job/enums.proto +++ b/core/proto/android/app/job/enums.proto @@ -18,6 +18,9 @@ syntax = "proto2"; package android.app.job; +// This file is for JobScheduler enums inside the app directory. If you're +// adding enums for system-server-side code, use the file in +// frameworks/base/core/proto/android/server/job. option java_outer_classname = "JobProtoEnums"; option java_multiple_files = true; diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto index e1d96bbce68a..9cf5ea78090d 100644 --- a/core/proto/android/bluetooth/hci/enums.proto +++ b/core/proto/android/bluetooth/hci/enums.proto @@ -351,7 +351,7 @@ enum EventEnum { EVT_COMMAND_COMPLETE = 0x0E; EVT_COMMAND_STATUS = 0x0F; EVT_HARDWARE_ERROR = 0x10; - EVT_FLUSH_OCCURED = 0x11; + EVT_FLUSH_OCCURRED = 0x11; EVT_ROLE_CHANGE = 0x12; EVT_NUM_COMPL_DATA_PKTS = 0x13; EVT_MODE_CHANGE = 0x14; diff --git a/core/proto/android/hardware/biometrics/enums.proto b/core/proto/android/hardware/biometrics/enums.proto new file mode 100644 index 000000000000..91f2acbbaf03 --- /dev/null +++ b/core/proto/android/hardware/biometrics/enums.proto @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.hardware.biometrics; + +option java_outer_classname = "BiometricsProtoEnums"; +option java_multiple_files = true; + +// Logging constants for <Biometric>Service and BiometricService + +enum ModalityEnum { + MODALITY_UNKNOWN = 0; + MODALITY_FINGERPRINT = 1; // 1 << 0 + MODALITY_IRIS = 2; // 1 << 1 + MODALITY_FACE = 4; // 1 << 2 +} + +enum ClientEnum { + CLIENT_UNKNOWN = 0; + CLIENT_KEYGUARD = 1; + CLIENT_BIOMETRIC_PROMPT = 2; + CLIENT_FINGERPRINT_MANAGER = 3; // Deprecated API before BiometricPrompt was introduced +} + +enum ActionEnum { + ACTION_UNKNOWN = 0; + ACTION_ENROLL = 1; + ACTION_AUTHENTICATE = 2; + ACTION_ENUMERATE = 3; + ACTION_REMOVE = 4; +}
\ No newline at end of file diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index f06165cc7e00..7e7942e6ddf1 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -522,6 +522,8 @@ message GlobalSettingsProto { optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Packages that are whitelisted for ignoring location settings (during emergencies) + optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Location location = 69; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index aaf6c63b2978..f3733fdb3810 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -72,6 +72,9 @@ message SecureSettingsProto { // List of the accessibility services to which the user has granted // permission to put the device into touch exploration mode. optional SettingProto touch_exploration_granted_accessibility_services = 31; + // Settings for accessibility timeout + optional SettingProto non_interactive_ui_timeout_ms = 32 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto interactive_ui_timeout_ms = 33 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/proto/android/server/job/enums.proto b/core/proto/android/server/job/enums.proto new file mode 100644 index 000000000000..50fc0310ad99 --- /dev/null +++ b/core/proto/android/server/job/enums.proto @@ -0,0 +1,43 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package com.android.server.job; + +// This file is for JobScheduler enums inside the server directory. If you're +// adding enums for app-side code, use the file in +// frameworks/base/core/proto/android/app/job. +option java_outer_classname = "JobServerProtoEnums"; +option java_multiple_files = true; + +// Set of constraints that a job potentially needs satisfied before it can run. +// Defined in +// frameworks/base/services/core/java/com/android/server/job/controllers/JobStatus.java +enum ConstraintEnum { + CONSTRAINT_UNKNOWN = 0; + CONSTRAINT_CHARGING = 1; + CONSTRAINT_BATTERY_NOT_LOW = 2; + CONSTRAINT_STORAGE_NOT_LOW = 3; + CONSTRAINT_TIMING_DELAY = 4; + CONSTRAINT_DEADLINE = 5; + CONSTRAINT_IDLE = 6; + CONSTRAINT_CONNECTIVITY = 7; + CONSTRAINT_CONTENT_TRIGGER = 8; + CONSTRAINT_DEVICE_NOT_DOZING = 9; + CONSTRAINT_WITHIN_QUOTA = 10; + CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 11; +} diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 188769d930d1..6c9d13a572ee 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -29,6 +29,7 @@ import "frameworks/base/core/proto/android/net/networkrequest.proto"; import "frameworks/base/core/proto/android/os/bundle.proto"; import "frameworks/base/core/proto/android/os/persistablebundle.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; +import "frameworks/base/core/proto/android/server/job/enums.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; // Next tag: 21 @@ -757,21 +758,9 @@ message JobStatusDumpProto { } optional JobInfo job_info = 6; - enum Constraint { - CONSTRAINT_CHARGING = 1; - CONSTRAINT_BATTERY_NOT_LOW = 2; - CONSTRAINT_STORAGE_NOT_LOW = 3; - CONSTRAINT_TIMING_DELAY = 4; - CONSTRAINT_DEADLINE = 5; - CONSTRAINT_IDLE = 6; - CONSTRAINT_CONNECTIVITY = 7; - CONSTRAINT_CONTENT_TRIGGER = 8; - CONSTRAINT_DEVICE_NOT_DOZING = 9; - CONSTRAINT_WITHIN_QUOTA = 10; - } - repeated Constraint required_constraints = 7; - repeated Constraint satisfied_constraints = 8; - repeated Constraint unsatisfied_constraints = 9; + repeated ConstraintEnum required_constraints = 7; + repeated ConstraintEnum satisfied_constraints = 8; + repeated ConstraintEnum unsatisfied_constraints = 9; optional bool is_doze_whitelisted = 10; optional bool is_uid_active = 26; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4a54bd775387..25baa921e8c9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -43,7 +43,7 @@ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" /> - <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" /> + <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" /> @@ -3329,7 +3329,7 @@ <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to clear user data. + <!-- @SystemApi @TestApi Allows an application to clear user data. <p>Not for use by third-party applications @hide --> diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml index 43b3552ff8d4..949c12e7d1da 100644 --- a/core/res/res/values-night/themes_device_defaults.xml +++ b/core/res/res/values-night/themes_device_defaults.xml @@ -51,6 +51,10 @@ easier. --> <!-- DeviceDefault theme for a window that should look like the Settings app. --> <style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault"> + <item name="windowLightStatusBar">false</item> + <item name="navigationBarColor">@android:color/black</item> + <item name="windowLightNavigationBar">false</item> + <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item> <item name="colorBackground">@color/primary_dark_device_default_settings</item> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 49f2c84335c5..c05795de4751 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2211,6 +2211,10 @@ has expired, then assume the device is receiving insufficient current to charge effectively and terminate the dream. Use -1 to disable this safety feature. --> <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> + <!-- Limit of how long the device can remain unlocked due to attention checking. --> + <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes --> + <!-- How long we should wait until we give up on receiving an attention API callback. --> + <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds --> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 176e75d00114..47bd9da65870 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3575,4 +3575,8 @@ <java-symbol type="bool" name="config_cbrs_supported" /> <java-symbol type="bool" name="config_awareSettingAvailable" /> + + <!-- For Attention Service --> + <java-symbol type="integer" name="config_attentionMaximumExtension" /> + <java-symbol type="integer" name="config_attentionApiTimeout" /> </resources> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 8e8b07a9074b..9d04e6392fd0 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -53,7 +53,6 @@ LOCAL_JAVA_LIBRARIES := \ org.apache.http.legacy \ android.test.base \ android.test.mock \ - framework-oahl-backward-compatibility \ framework-atb-backward-compatibility \ LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index a15dbc80d7db..2a29f8336adc 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -131,6 +131,10 @@ public class SettingsBackupTest { Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, Settings.Global.AUTOMATIC_POWER_SAVER_MODE, Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + Settings.Global.BROADCAST_BG_CONSTANTS, + Settings.Global.BROADCAST_FG_CONSTANTS, + Settings.Global.BROADCAST_OFFLOAD_CONSTANTS, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java new file mode 100644 index 000000000000..b07cb99f35c5 --- /dev/null +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -0,0 +1,145 @@ +/* + * 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.view; + +import static android.view.ImeInsetsSourceConsumer.areEditorsSimilar; +import static android.view.InsetsState.TYPE_IME; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Bundle; +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl.Transaction; +import android.view.WindowManager.BadTokenException; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.TextView; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class ImeInsetsSourceConsumerTest { + + Context mContext = InstrumentationRegistry.getTargetContext(); + ImeInsetsSourceConsumer mImeConsumer; + InsetsController mController; + SurfaceControl mLeash; + + @Before + public void setup() { + mLeash = new SurfaceControl.Builder(new SurfaceSession()) + .setName("testSurface") + .build(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplay()); + try { + viewRootImpl.setView(new TextView(mContext), new LayoutParams(), null); + } catch (BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mController = new InsetsController(viewRootImpl); + final Rect rect = new Rect(5, 5, 5, 5); + mController.calculateInsets( + false, + false, + new DisplayCutout( + Insets.of(10, 10, 10, 10), rect, rect, rect, rect), + rect, rect); + mImeConsumer = new ImeInsetsSourceConsumer( + new InsetsState(), Transaction::new, mController); + }); + } + + @Test + public void testImeVisibility() { + final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash); + mController.onControlsChanged(new InsetsSourceControl[] { ime }); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // test if setVisibility can show IME + mImeConsumer.onWindowFocusGained(); + mImeConsumer.applyImeVisibility(true); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + // test if setVisibility can hide IME + mImeConsumer.applyImeVisibility(false); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + }); + } + + @Test + public void testAreEditorsSimilar() { + EditorInfo info1 = new EditorInfo(); + info1.privateImeOptions = "dummy"; + EditorInfo info2 = new EditorInfo(); + + assertFalse(areEditorsSimilar(info1, info2)); + + info1.privateImeOptions = null; + assertTrue(areEditorsSimilar(info1, info2)); + + info1.inputType = info2.inputType = 3; + info1.imeOptions = info2.imeOptions = 0x4; + info1.packageName = info2.packageName = "dummy.package"; + assertTrue(areEditorsSimilar(info1, info2)); + + Bundle extras1 = new Bundle(); + extras1.putByteArray("key1", "value1".getBytes()); + extras1.putChar("key2", 'c'); + Bundle extras2 = new Bundle(); + extras2.putByteArray("key1", "value1".getBytes()); + extras2.putChar("key2", 'c'); + info1.extras = extras1; + info2.extras = extras2; + assertTrue(areEditorsSimilar(info1, info2)); + + Bundle extraBundle = new Bundle(); + ArrayList<Integer> list = new ArrayList<>(); + list.add(2); + list.add(5); + extraBundle.putByteArray("key1", "value1".getBytes()); + extraBundle.putChar("key2", 'c'); + extraBundle.putIntegerArrayList("key3", list); + + extras1.putAll(extraBundle); + extras2.putAll(extraBundle); + assertTrue(areEditorsSimilar(info1, info2)); + + extras2.putChar("key2", 'd'); + assertFalse(areEditorsSimilar(info1, info2)); + + extras2.putChar("key2", 'c'); + extras2.putInt("key4", 1); + assertFalse(areEditorsSimilar(info1, info2)); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 2db2f5f2a4fd..03af67df13d0 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -155,10 +155,10 @@ public class InsetsStateTest { @Test public void testGetDefaultVisibility() { - assertTrue(InsetsState.getDefaultVisibly(TYPE_TOP_BAR)); - assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_1)); - assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_2)); - assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_3)); - assertFalse(InsetsState.getDefaultVisibly(TYPE_IME)); + assertTrue(InsetsState.getDefaultVisibility(TYPE_TOP_BAR)); + assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_1)); + assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_2)); + assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_3)); + assertFalse(InsetsState.getDefaultVisibility(TYPE_IME)); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 9662182b8cf8..32bafec4081a 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -16,9 +16,7 @@ package android.view.textclassifier; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertWithMessage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -30,6 +28,8 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TextClassificationConstantsTest { + private static final float EPSILON = 0.0001f; + @Test public void testLoadFromString() { final String s = "local_textclassifier_enabled=true," @@ -42,26 +42,55 @@ public class TextClassificationConstantsTest { + "suggest_selection_max_range_length=10," + "classify_text_max_range_length=11," + "generate_links_max_text_length=12," - + "generate_links_log_sample_rate=13"; + + "generate_links_log_sample_rate=13," + + "entity_list_default=phone," + + "entity_list_not_editable=address:flight," + + "entity_list_editable=date:datetime," + + "in_app_conversation_action_types_default=text_reply," + + "notification_conversation_action_types_default=send_email:call_phone," + + "lang_id_threshold_override=0.3"; final TextClassificationConstants constants = TextClassificationConstants.loadFromString(s); - assertTrue("local_textclassifier_enabled", - constants.isLocalTextClassifierEnabled()); - assertTrue("system_textclassifier_enabled", - constants.isSystemTextClassifierEnabled()); - assertTrue("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled()); - assertTrue("smart_selection_enabled", constants.isSmartSelectionEnabled()); - assertTrue("smart_text_share_enabled", constants.isSmartTextShareEnabled()); - assertTrue("smart_linkify_enabled", constants.isSmartLinkifyEnabled()); - assertTrue("smart_select_animation_enabled", constants.isSmartSelectionAnimationEnabled()); - assertEquals("suggest_selection_max_range_length", - 10, constants.getSuggestSelectionMaxRangeLength()); - assertEquals("classify_text_max_range_length", - 11, constants.getClassifyTextMaxRangeLength()); - assertEquals("generate_links_max_text_length", - 12, constants.getGenerateLinksMaxTextLength()); - assertEquals("generate_links_log_sample_rate", - 13, constants.getGenerateLinksLogSampleRate()); + + assertWithMessage("local_textclassifier_enabled") + .that(constants.isLocalTextClassifierEnabled()).isTrue(); + assertWithMessage("system_textclassifier_enabled") + .that(constants.isSystemTextClassifierEnabled()).isTrue(); + assertWithMessage("model_dark_launch_enabled") + .that(constants.isModelDarkLaunchEnabled()).isTrue(); + assertWithMessage("smart_selection_enabled") + .that(constants.isSmartSelectionEnabled()).isTrue(); + assertWithMessage("smart_text_share_enabled") + .that(constants.isSmartTextShareEnabled()).isTrue(); + assertWithMessage("smart_linkify_enabled") + .that(constants.isSmartLinkifyEnabled()).isTrue(); + assertWithMessage("smart_select_animation_enabled") + .that(constants.isSmartSelectionAnimationEnabled()).isTrue(); + assertWithMessage("suggest_selection_max_range_length") + .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10); + assertWithMessage("classify_text_max_range_length") + .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(11); + assertWithMessage("generate_links_max_text_length") + .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(12); + assertWithMessage("generate_links_log_sample_rate") + .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(13); + assertWithMessage("entity_list_default") + .that(constants.getEntityListDefault()) + .containsExactly("phone"); + assertWithMessage("entity_list_not_editable") + .that(constants.getEntityListNotEditable()) + .containsExactly("address", "flight"); + assertWithMessage("entity_list_editable") + .that(constants.getEntityListEditable()) + .containsExactly("date", "datetime"); + assertWithMessage("in_app_conversation_action_types_default") + .that(constants.getInAppConversationActionTypes()) + .containsExactly("text_reply"); + assertWithMessage("notification_conversation_action_types_default") + .that(constants.getNotificationConversationActionTypes()) + .containsExactly("send_email", "call_phone"); + assertWithMessage("lang_id_threshold_override") + .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f); } @Test @@ -76,42 +105,102 @@ public class TextClassificationConstantsTest { + "suggest_selection_max_range_length=8," + "classify_text_max_range_length=7," + "generate_links_max_text_length=6," - + "generate_links_log_sample_rate=5"; + + "generate_links_log_sample_rate=5," + + "entity_list_default=email:url," + + "entity_list_not_editable=date," + + "entity_list_editable=flight," + + "in_app_conversation_action_types_default=view_map:track_flight," + + "notification_conversation_action_types_default=share_location," + + "lang_id_threshold_override=2"; final TextClassificationConstants constants = TextClassificationConstants.loadFromString(s); - assertFalse("local_textclassifier_enabled", - constants.isLocalTextClassifierEnabled()); - assertFalse("system_textclassifier_enabled", - constants.isSystemTextClassifierEnabled()); - assertFalse("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled()); - assertFalse("smart_selection_enabled", constants.isSmartSelectionEnabled()); - assertFalse("smart_text_share_enabled", constants.isSmartTextShareEnabled()); - assertFalse("smart_linkify_enabled", constants.isSmartLinkifyEnabled()); - assertFalse("smart_select_animation_enabled", - constants.isSmartSelectionAnimationEnabled()); - assertEquals("suggest_selection_max_range_length", - 8, constants.getSuggestSelectionMaxRangeLength()); - assertEquals("classify_text_max_range_length", - 7, constants.getClassifyTextMaxRangeLength()); - assertEquals("generate_links_max_text_length", - 6, constants.getGenerateLinksMaxTextLength()); - assertEquals("generate_links_log_sample_rate", - 5, constants.getGenerateLinksLogSampleRate()); + + assertWithMessage("local_textclassifier_enabled") + .that(constants.isLocalTextClassifierEnabled()).isFalse(); + assertWithMessage("system_textclassifier_enabled") + .that(constants.isSystemTextClassifierEnabled()).isFalse(); + assertWithMessage("model_dark_launch_enabled") + .that(constants.isModelDarkLaunchEnabled()).isFalse(); + assertWithMessage("smart_selection_enabled") + .that(constants.isSmartSelectionEnabled()).isFalse(); + assertWithMessage("smart_text_share_enabled") + .that(constants.isSmartTextShareEnabled()).isFalse(); + assertWithMessage("smart_linkify_enabled") + .that(constants.isSmartLinkifyEnabled()).isFalse(); + assertWithMessage("smart_select_animation_enabled") + .that(constants.isSmartSelectionAnimationEnabled()).isFalse(); + assertWithMessage("suggest_selection_max_range_length") + .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8); + assertWithMessage("classify_text_max_range_length") + .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(7); + assertWithMessage("generate_links_max_text_length") + .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(6); + assertWithMessage("generate_links_log_sample_rate") + .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(5); + assertWithMessage("entity_list_default") + .that(constants.getEntityListDefault()) + .containsExactly("email", "url"); + assertWithMessage("entity_list_not_editable") + .that(constants.getEntityListNotEditable()) + .containsExactly("date"); + assertWithMessage("entity_list_editable") + .that(constants.getEntityListEditable()) + .containsExactly("flight"); + assertWithMessage("in_app_conversation_action_types_default") + .that(constants.getInAppConversationActionTypes()) + .containsExactly("view_map", "track_flight"); + assertWithMessage("notification_conversation_action_types_default") + .that(constants.getNotificationConversationActionTypes()) + .containsExactly("share_location"); + assertWithMessage("lang_id_threshold_override") + .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f); } @Test - public void testEntityListParsing() { - final TextClassificationConstants constants = TextClassificationConstants.loadFromString( - "entity_list_default=phone," - + "entity_list_not_editable=address:flight," - + "entity_list_editable=date:datetime"); - assertEquals(1, constants.getEntityListDefault().size()); - assertEquals("phone", constants.getEntityListDefault().get(0)); - assertEquals(2, constants.getEntityListNotEditable().size()); - assertEquals("address", constants.getEntityListNotEditable().get(0)); - assertEquals("flight", constants.getEntityListNotEditable().get(1)); - assertEquals(2, constants.getEntityListEditable().size()); - assertEquals("date", constants.getEntityListEditable().get(0)); - assertEquals("datetime", constants.getEntityListEditable().get(1)); + public void testLoadFromString_defaultValues() { + final TextClassificationConstants constants = + TextClassificationConstants.loadFromString(""); + + assertWithMessage("local_textclassifier_enabled") + .that(constants.isLocalTextClassifierEnabled()).isTrue(); + assertWithMessage("system_textclassifier_enabled") + .that(constants.isSystemTextClassifierEnabled()).isTrue(); + assertWithMessage("model_dark_launch_enabled") + .that(constants.isModelDarkLaunchEnabled()).isFalse(); + assertWithMessage("smart_selection_enabled") + .that(constants.isSmartSelectionEnabled()).isTrue(); + assertWithMessage("smart_text_share_enabled") + .that(constants.isSmartTextShareEnabled()).isTrue(); + assertWithMessage("smart_linkify_enabled") + .that(constants.isSmartLinkifyEnabled()).isTrue(); + assertWithMessage("smart_select_animation_enabled") + .that(constants.isSmartSelectionAnimationEnabled()).isTrue(); + assertWithMessage("suggest_selection_max_range_length") + .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10 * 1000); + assertWithMessage("classify_text_max_range_length") + .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(10 * 1000); + assertWithMessage("generate_links_max_text_length") + .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(100 * 1000); + assertWithMessage("generate_links_log_sample_rate") + .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(100); + assertWithMessage("entity_list_default") + .that(constants.getEntityListDefault()) + .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); + assertWithMessage("entity_list_not_editable") + .that(constants.getEntityListNotEditable()) + .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); + assertWithMessage("entity_list_editable") + .that(constants.getEntityListEditable()) + .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); + assertWithMessage("in_app_conversation_action_types_default") + .that(constants.getInAppConversationActionTypes()) + .containsExactly("text_reply", "create_reminder", "call_phone", "open_url", + "send_email", "send_sms", "track_flight", "view_calendar", "view_map"); + assertWithMessage("notification_conversation_action_types_default") + .that(constants.getNotificationConversationActionTypes()) + .containsExactly("text_reply", "create_reminder", "call_phone", "open_url", + "send_email", "send_sms", "track_flight", "view_calendar", "view_map"); + assertWithMessage("lang_id_threshold_override") + .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 904c3fb6d549..1684138455e5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -87,6 +87,7 @@ applications that come with the platform <permission name="android.permission.MASTER_CLEAR"/> <permission name="android.permission.NETWORK_MANAGED_PROVISIONING"/> <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.SET_TIME"/> <permission name="android.permission.SET_TIME_ZONE"/> <permission name="android.permission.SHUTDOWN"/> diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 4755d45cd434..c9e46942a51a 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1821,6 +1821,8 @@ public abstract class ColorSpace { * @param cct The correlated color temperature, in Kelvin * @return Corresponding XYZ values * @throws IllegalArgumentException If cct is invalid + * + * @hide */ @NonNull @Size(3) @@ -1851,6 +1853,8 @@ public abstract class ColorSpace { * @param srcWhitePoint The white point to adapt from * @param dstWhitePoint The white point to adapt to * @return A 3x3 matrix as a non-null array of 9 floats + * + * @hide */ @NonNull @Size(9) diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index c580c46cc888..0787d8518fa5 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -183,6 +183,14 @@ public class ImageFormat { public static final int JPEG = 0x100; /** + * Depth augmented compressed JPEG format. + * + * <p>JPEG compressed main image along with XMP embedded depth metadata + * following ISO 16684-1:2011(E).</p> + */ + public static final int DEPTH_JPEG = 0x69656963; + + /** * <p>Multi-plane Android YUV 420 format</p> * * <p>This format is a generic YCbCr format, capable of describing any 4:2:0 @@ -787,6 +795,7 @@ public class ImageFormat { case PRIVATE: case RAW_DEPTH: case Y8: + case DEPTH_JPEG: return true; } diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 072fe7321826..2ae28f507e0d 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -16,12 +16,6 @@ package android.security; -import android.annotation.UnsupportedAppUsage; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - import com.android.org.bouncycastle.util.io.pem.PemObject; import com.android.org.bouncycastle.util.io.pem.PemReader; import com.android.org.bouncycastle.util.io.pem.PemWriter; @@ -34,7 +28,6 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.security.KeyPair; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -53,8 +46,6 @@ public class Credentials { public static final String INSTALL_AS_USER_ACTION = "android.credentials.INSTALL_AS_USER"; - public static final String UNLOCK_ACTION = "com.android.credentials.UNLOCK"; - /** Key prefix for CA certificates. */ public static final String CA_CERTIFICATE = "CACERT_"; @@ -171,58 +162,6 @@ public class Credentials { } } - private static Credentials singleton; - - @UnsupportedAppUsage - public static Credentials getInstance() { - if (singleton == null) { - singleton = new Credentials(); - } - return singleton; - } - - @UnsupportedAppUsage - public void unlock(Context context) { - try { - Intent intent = new Intent(UNLOCK_ACTION); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(LOGTAG, e.toString()); - } - } - - public void install(Context context) { - try { - Intent intent = KeyChain.createInstallIntent(); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(LOGTAG, e.toString()); - } - } - - @UnsupportedAppUsage - public void install(Context context, KeyPair pair) { - try { - Intent intent = KeyChain.createInstallIntent(); - intent.putExtra(EXTRA_PRIVATE_KEY, pair.getPrivate().getEncoded()); - intent.putExtra(EXTRA_PUBLIC_KEY, pair.getPublic().getEncoded()); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(LOGTAG, e.toString()); - } - } - - @UnsupportedAppUsage - public void install(Context context, String type, byte[] value) { - try { - Intent intent = KeyChain.createInstallIntent(); - intent.putExtra(type, value); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(LOGTAG, e.toString()); - } - } - /** * Delete all types (private key, user certificate, CA certificate) for a * particular {@code alias}. All three can exist for any given alias. diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index d2903f08af15..2f2d575bca29 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -99,7 +99,7 @@ void Paint::setAntiAlias(bool aa) { ////////////////// Java flags compatibility ////////////////// -/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really match up with skia anymore, so we have to do some shuffling in get/set flags() 3 flags apply to SkPaint (antialias, dither, filter -> enum) diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java index a2feec2e9faf..6a90c176c2df 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -4482,6 +4482,7 @@ public class MediaPlayer2 implements AutoCloseable }; // Modular DRM + private final Map<UUID, MediaDrm> mDrmObjs = new HashMap<>(); private class DrmHandle { static final int PROVISION_TIMEOUT_MS = 60000; @@ -4594,7 +4595,13 @@ public class MediaPlayer2 implements AutoCloseable Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); try { - mDrmObj = new MediaDrm(uuid); + mDrmObj = mDrmObjs.computeIfAbsent(uuid, scheme -> { + try { + return new MediaDrm(scheme); + } catch (UnsupportedSchemeException e) { + throw new IllegalArgumentException(e); + } + }); Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); } catch (Exception e) { // UnsupportedSchemeException Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b7f042b48da1..f996d38c86a9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -30,6 +30,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -3989,33 +3990,11 @@ public class AudioManager { } /** - * Indicate A2DP source or sink connection state change. - * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) - * @param profile profile for the A2DP device - * (either {@link android.bluetooth.BluetoothProfile.A2DP} or - * {@link android.bluetooth.BluetoothProfile.A2DP_SINK}) - * @return a delay in ms that the caller should wait before broadcasting - * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent. - * {@hide} - */ - public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, - int profile) { - final IAudioService service = getService(); - int delay = 0; - try { - delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return delay; - } - - /** * Indicate A2DP source or sink connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) + * @param state new connection state, {@link BluetoothProfile#STATE_CONNECTED} + * or {@link BluetoothProfile#STATE_DISCONNECTED} * @param profile profile for the A2DP device * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting. * (either {@link android.bluetooth.BluetoothProfile.A2DP} or @@ -4027,8 +4006,8 @@ public class AudioManager { * {@hide} */ public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - BluetoothDevice device, int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { + BluetoothDevice device, int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { final IAudioService service = getService(); int delay = 0; try { diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index af016d5d4be9..ffa3b247480a 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -39,6 +39,8 @@ import java.util.Map; */ public class AudioSystem { + private static final boolean DEBUG_VOLUME = true; + private static final String TAG = "AudioSystem"; /* These values must be kept in sync with system/audio.h */ /* @@ -879,6 +881,15 @@ public class AudioSystem } } + /** Wrapper for native methods called from AudioService */ + public static int setStreamVolumeIndexAS(int stream, int index, int device) { + if (DEBUG_VOLUME) { + Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream] + + " dev=" + Integer.toHexString(device) + " idx=" + index); + } + return setStreamVolumeIndex(stream, index, device); + } + // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t public static final int SYNC_EVENT_NONE = 0; public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; @@ -906,7 +917,7 @@ public class AudioSystem @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage - public static native int setStreamVolumeIndex(int stream, int index, int device); + private static native int setStreamVolumeIndex(int stream, int index, int device); public static native int getStreamVolumeIndex(int stream, int device); public static native int setMasterVolume(float value); public static native float getMasterVolume(); diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 5e77fdf0a121..3440cde415d5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -996,6 +996,50 @@ public class AudioTrack extends PlayerBase } /** + * Configures the delay and padding values for the current compressed stream playing + * in offload mode. + * This can only be used on a track successfully initialized with + * {@link AudioTrack.Builder#setOffloadedPlayback(boolean)}. The unit is frames, where a + * frame indicates the number of samples per channel, e.g. 100 frames for a stereo compressed + * stream corresponds to 200 decoded interleaved PCM samples. + * @param delayInFrames number of frames to be ignored at the beginning of the stream. A value + * of 0 indicates no padding is to be applied. + * @param paddingInFrames number of frames to be ignored at the end of the stream. A value of 0 + * of 0 indicates no delay is to be applied. + */ + public void setOffloadDelayPadding(int delayInFrames, int paddingInFrames) { + if (paddingInFrames < 0) { + throw new IllegalArgumentException("Illegal negative padding"); + } + if (delayInFrames < 0) { + throw new IllegalArgumentException("Illegal negative delay"); + } + if (!mOffloaded) { + throw new IllegalStateException("Illegal use of delay/padding on non-offloaded track"); + } + if (mState == STATE_UNINITIALIZED) { + throw new IllegalStateException("Uninitialized track"); + } + native_set_delay_padding(delayInFrames, paddingInFrames); + } + + /** + * Declares that the last write() operation on this track provided the last buffer of this + * stream. + * After the end of stream, previously set padding and delay values are ignored. + * Use this method in the same thread as any write() operation. + */ + public void setOffloadEndOfStream() { + if (!mOffloaded) { + throw new IllegalStateException("EOS not supported on non-offloaded track"); + } + if (mState == STATE_UNINITIALIZED) { + throw new IllegalStateException("Uninitialized track"); + } + native_set_eos(); + } + + /** * Returns whether direct playback of an audio format with the provided attributes is * currently supported on the system. * <p>Direct playback means that the audio stream is not resampled or downmixed @@ -3457,6 +3501,9 @@ public class AudioTrack extends PlayerBase private native int native_getPortId(); + private native void native_set_delay_padding(int delayInFrames, int paddingInFrames); + private native void native_set_eos(); + //--------------------------------------------------------- // Utility methods //------------------ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 14bdab98d46b..f5aeca717424 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -151,8 +151,6 @@ interface IAudioService { void setWiredDeviceConnectionState(int type, int state, String address, String name, String caller); - int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile); - void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device, diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 9ac147b59ee5..60ef1d93191a 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -17,12 +17,10 @@ package android.media; import android.graphics.ImageFormat; -import android.graphics.PixelFormat; import android.hardware.HardwareBuffer; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.util.Log; import android.view.Surface; import dalvik.system.VMRuntime; @@ -74,12 +72,6 @@ public class ImageReader implements AutoCloseable { private static final int ACQUIRE_MAX_IMAGES = 2; /** - * Invalid consumer buffer usage flag. This usage flag will be ignored - * by the {@code ImageReader} instance is constructed with this value. - */ - private static final long BUFFER_USAGE_UNKNOWN = 0; - - /** * <p> * Create a new reader for images of the desired size and format. * </p> @@ -129,7 +121,10 @@ public class ImageReader implements AutoCloseable { * @see Image */ public static ImageReader newInstance(int width, int height, int format, int maxImages) { - return new ImageReader(width, height, format, maxImages, BUFFER_USAGE_UNKNOWN); + // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not + // work, and is inscrutable anyway + return new ImageReader(width, height, format, maxImages, + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN); } /** @@ -207,18 +202,23 @@ public class ImageReader implements AutoCloseable { * obtained by the user, one of them has to be released before a new Image will * become available for access through {@link #acquireLatestImage()} or * {@link #acquireNextImage()}. Must be greater than 0. - * @param usage The intended usage of the images produced by this ImageReader. It needs - * to be one of the Usage defined by {@link HardwareBuffer}, or an - * {@link IllegalArgumentException} will be thrown. + * @param usage The intended usage of the images produced by this ImageReader. See the usages + * on {@link HardwareBuffer} for a list of valid usage bits. See also + * {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking + * if a combination is supported. If it's not supported this will throw + * an {@link IllegalArgumentException}. * @see Image * @see HardwareBuffer */ public static ImageReader newInstance(int width, int height, int format, int maxImages, long usage) { - if (!isFormatUsageCombinationAllowed(format, usage)) { - throw new IllegalArgumentException("Format usage combination is not supported:" - + " format = " + format + ", usage = " + usage); - } + // TODO: Check this - can't do it just yet because format support is different + // Unify formats! The only reliable way to validate usage is to just try it and see. + +// if (!HardwareBuffer.isSupported(width, height, format, 1, usage)) { +// throw new IllegalArgumentException("The given format=" + Integer.toHexString(format) +// + " & usage=" + Long.toHexString(usage) + " is not supported"); +// } return new ImageReader(width, height, format, maxImages, usage); } @@ -716,19 +716,6 @@ public class ImageReader implements AutoCloseable { return si.getReader() == this; } - private static boolean isFormatUsageCombinationAllowed(int format, long usage) { - if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { - return false; - } - - // Valid usage needs to be provided. - if (usage == BUFFER_USAGE_UNKNOWN) { - return false; - } - - return true; - } - /** * Called from Native code when an Event happens. * @@ -833,6 +820,7 @@ public class ImageReader implements AutoCloseable { case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: case ImageFormat.RAW_PRIVATE: + case ImageFormat.DEPTH_JPEG: width = ImageReader.this.getWidth(); break; default: @@ -849,6 +837,7 @@ public class ImageReader implements AutoCloseable { case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: case ImageFormat.RAW_PRIVATE: + case ImageFormat.DEPTH_JPEG: height = ImageReader.this.getHeight(); break; default: diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index 2a0e04ebf051..b77a884d3412 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -63,6 +63,7 @@ class ImageUtils { case ImageFormat.DEPTH16: case ImageFormat.DEPTH_POINT_CLOUD: case ImageFormat.RAW_DEPTH: + case ImageFormat.DEPTH_JPEG: return 1; case ImageFormat.PRIVATE: return 0; @@ -192,6 +193,7 @@ class ImageUtils { // 10x compression from RGB_888 case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: + case ImageFormat.DEPTH_JPEG: estimatedBytePerPixel = 0.3; break; case ImageFormat.Y8: diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 4eed12f428bc..33e7d8ea1b94 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -335,7 +335,7 @@ public class MediaScanner implements AutoCloseable { private final Uri mPlaylistsUri; @UnsupportedAppUsage private final Uri mFilesUri; - private final Uri mFilesUriNoNotify; + private final Uri mFilesFullUri; private final boolean mProcessPlaylists; private final boolean mProcessGenres; private int mMtpObjectHandle; @@ -445,7 +445,11 @@ public class MediaScanner implements AutoCloseable { mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); mFilesUri = Files.getContentUri(volumeName); - mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); + + Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); + filesFullUri = MediaStore.setIncludePending(filesFullUri); + filesFullUri = MediaStore.setIncludeTrashed(filesFullUri); + mFilesFullUri = filesFullUri; if (!volumeName.equals("internal")) { // we only support playlists on external media @@ -1625,7 +1629,7 @@ public class MediaScanner implements AutoCloseable { try { where = Files.FileColumns.DATA + "=?"; selectionArgs = new String[] { path }; - c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION, + c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION, where, selectionArgs, null, null); if (c != null && c.moveToFirst()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 21c58b377e94..09b755912c43 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -20,9 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.StringDef; import android.annotation.SystemApi; -import android.annotation.SdkConstant.SdkConstantType; import android.app.Activity; import android.content.ComponentName; import android.content.ContentResolver; @@ -1680,6 +1680,7 @@ public final class TvContract { TYPE_ISDB_T, TYPE_ISDB_TB, TYPE_ISDB_S, + TYPE_ISDB_S3, TYPE_ISDB_C, TYPE_1SEG, TYPE_DTMB, @@ -1821,6 +1822,13 @@ public final class TvContract { public static final String TYPE_ISDB_S = "TYPE_ISDB_S"; /** + * The channel type for ISDB-S3 (satellite). + * + * @see #COLUMN_TYPE + */ + public static final String TYPE_ISDB_S3 = "TYPE_ISDB_S3"; + + /** * The channel type for ISDB-C (cable). * * @see #COLUMN_TYPE @@ -2026,6 +2034,7 @@ public final class TvContract { * {@link #TYPE_DVB_T2}, * {@link #TYPE_ISDB_C}, * {@link #TYPE_ISDB_S}, + * {@link #TYPE_ISDB_S3}, * {@link #TYPE_ISDB_T}, * {@link #TYPE_ISDB_TB}, * {@link #TYPE_NTSC}, diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 48dbf555e546..0fb4d2a5e446 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -94,26 +94,14 @@ cc_library_shared { ], shared_libs: [ - // MediaCas - "android.hardware.cas@1.0", - "android.hardware.cas.native@1.0", - "android.hidl.allocator@1.0", - "libhidlbase", - "libhidlmemory", - - "libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API. - "libbinder", // Used by JWakeLock and MediaMetrics. - - "libutils", // Have to use shared lib to make libandroid_runtime behave correctly. - // Otherwise, AndroidRuntime::getJNIEnv() will return NULL. - - // NDK or NDK-compliant + // NDK or LLNDK or NDK-compliant "libandroid", "libbinder_ndk", "libmediandk", + "libmediametrics", "libnativehelper_compat_libc++", "liblog", - "libz", + "libvndksupport", ], header_libs: [ @@ -122,6 +110,16 @@ cc_library_shared { ], static_libs: [ + // MediaCas + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libhidlbase", + "libhidlmemory", + "libhidltransport", + "libhwbinder", + "libbinderthreadstate", + + // MediaPlayer2 implementation "libbase", "libcrypto", "libcutils", @@ -131,10 +129,11 @@ cc_library_shared { "libmediaplayer2-protos", "libmediandk_utils", "libmediautils", + "libprocessgroup", "libprotobuf-cpp-lite", "libstagefright", "libstagefright_esds", - "libstagefright_foundation", + "libstagefright_foundation_without_imemory", "libstagefright_httplive", "libstagefright_id3", "libstagefright_mpeg2support", @@ -142,6 +141,7 @@ cc_library_shared { "libstagefright_player2", "libstagefright_rtsp_player2", "libstagefright_timedtext2", + "libutils", "libmedia2_jni_core", ], @@ -161,6 +161,7 @@ cc_library_shared { "-Wno-error=deprecated-declarations", "-Wunused", "-Wunreachable-code", + "-fvisibility=hidden", ], ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"], diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index a45aa90f5f19..7168b2dadf92 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -379,24 +379,9 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d", width, height, format, maxImages, getpid(), createProcessUniqueId()); - uint32_t consumerUsage = GRALLOC_USAGE_SW_READ_OFTEN; - bool needUsageOverride = ndkUsage != CONSUMER_BUFFER_USAGE_UNKNOWN; - uint64_t outProducerUsage = 0; - uint64_t outConsumerUsage = 0; - android_hardware_HardwareBuffer_convertToGrallocUsageBits(&outProducerUsage, &outConsumerUsage, - ndkUsage, 0); - - if (isFormatOpaque(nativeFormat)) { - // Use the SW_READ_NEVER usage to tell producer that this format is not for preview or video - // encoding. The only possibility will be ZSL output. - consumerUsage = GRALLOC_USAGE_SW_READ_NEVER; - if (needUsageOverride) { - consumerUsage = android_convertGralloc1To0Usage(0, outConsumerUsage); - } - } else if (needUsageOverride) { - ALOGW("Consumer usage override for non-opaque format is not implemented yet, " - "ignore the provided usage from the application"); - } + uint64_t consumerUsage = + android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage); + bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages, /*controlledByApp*/true); if (bufferConsumer == nullptr) { @@ -405,6 +390,11 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w nativeFormat, consumerUsage); return; } + + if (consumerUsage & GRALLOC_USAGE_PROTECTED) { + gbConsumer->setConsumerIsProtected(true); + } + ctx->setBufferConsumer(bufferConsumer); bufferConsumer->setName(consumerName); diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp index 3ded8c260512..de60b085b87d 100644 --- a/media/jni/android_media_MediaMetricsJNI.cpp +++ b/media/jni/android_media_MediaMetricsJNI.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "MediaMetricsJNI" + #include <jni.h> #include <nativehelper/JNIHelp.h> @@ -21,7 +23,10 @@ #include <media/MediaAnalyticsItem.h> -// Copeid from core/jni/ (libandroid_runtime.so) +// This source file is compiled and linked into both: +// core/jni/ (libandroid_runtime.so) +// media/jni (libmedia2_jni.so) + namespace android { // place the attributes into a java PersistableBundle object @@ -29,7 +34,7 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); + ALOGE("can't find android/os/PersistableBundle"); return NULL; } // sometimes the caller provides one for us to fill @@ -86,5 +91,138 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i return mybundle; } +// convert the specified batch metrics attributes to a persistent bundle. +// The encoding of the byte array is specified in +// frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +// +// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +enum { kInt32 = 0, kInt64, kDouble, kRate, kCString}; + +jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) { + ALOGV("writeAttributes()"); + + if (buffer == NULL || length <= 0) { + ALOGW("bad parameters to writeAttributesToBundle()"); + return NULL; + } + + jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); + if (clazzBundle==NULL) { + ALOGE("can't find android/os/PersistableBundle"); + return NULL; + } + // sometimes the caller provides one for us to fill + if (mybundle == NULL) { + // create the bundle + jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); + mybundle = env->NewObject(clazzBundle, constructID); + if (mybundle == NULL) { + ALOGD("unable to create mybundle"); + return NULL; + } + } + + int left = length; + char *buf = buffer; + + // grab methods that we can invoke + jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); + jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); + jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); + jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); + + +#define _EXTRACT(size, val) \ + { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);} +#define _SKIP(size) \ + { if ((size) > left) goto badness; buf += (size); left -= (size);} + + int32_t bufsize; + _EXTRACT(sizeof(int32_t), bufsize); + if (bufsize != length) { + goto badness; + } + int32_t proto; + _EXTRACT(sizeof(int32_t), proto); + if (proto != 0) { + ALOGE("unsupported wire protocol %d", proto); + goto badness; + } + + int32_t count; + _EXTRACT(sizeof(int32_t), count); + + // iterate through my attributes + // -- get name, get type, get value, insert into bundle appropriately. + for (int i = 0 ; i < count; i++ ) { + // prop name len (int16) + int16_t keylen; + _EXTRACT(sizeof(int16_t), keylen); + if (keylen <= 0) goto badness; + // prop name itself + char *key = buf; + jstring keyName = env->NewStringUTF(buf); + _SKIP(keylen); + + // prop type (int8_t) + int8_t attrType; + _EXTRACT(sizeof(int8_t), attrType); + + int16_t attrSize; + _EXTRACT(sizeof(int16_t), attrSize); + + switch (attrType) { + case kInt32: + { + int32_t i32; + _EXTRACT(sizeof(int32_t), i32); + env->CallVoidMethod(mybundle, setIntID, + keyName, (jint) i32); + break; + } + case kInt64: + { + int64_t i64; + _EXTRACT(sizeof(int64_t), i64); + env->CallVoidMethod(mybundle, setLongID, + keyName, (jlong) i64); + break; + } + case kDouble: + { + double d64; + _EXTRACT(sizeof(double), d64); + env->CallVoidMethod(mybundle, setDoubleID, + keyName, (jdouble) d64); + break; + } + case kCString: + { + jstring value = env->NewStringUTF(buf); + env->CallVoidMethod(mybundle, setStringID, + keyName, value); + _SKIP(attrSize); + break; + } + default: + ALOGW("ignoring Attribute '%s' unknown type: %d", + key, attrType); + _SKIP(attrSize); + break; + } + } + + // should have consumed it all + if (left != 0) { + ALOGW("did not consume entire buffer; left(%d) != 0", left); + goto badness; + } + + return mybundle; + + badness: + return NULL; +} + }; // namespace android diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h index fd621ea7261d..a10780f5c5c3 100644 --- a/media/jni/android_media_MediaMetricsJNI.h +++ b/media/jni/android_media_MediaMetricsJNI.h @@ -27,6 +27,7 @@ namespace android { class MediaMetricsJNI { public: static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); + static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length); }; }; // namespace android diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 9b4e730cfb5e..306916121740 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -786,22 +786,17 @@ android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz) return 0; } - Parcel p; - int key = FOURCC('m','t','r','X'); - status_t status = mp->getParameter(key, &p); + char *buffer = NULL; + size_t length = 0; + status_t status = mp->getMetrics(&buffer, &length); if (status != OK) { ALOGD("getMetrics() failed: %d", status); return (jobject) NULL; } - p.setDataPosition(0); - MediaAnalyticsItem *item = new MediaAnalyticsItem; - item->readFromParcel(p); - jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); + jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length); - // housekeeping - delete item; - item = NULL; + free(buffer); return mybundle; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java index 5ab50925a268..0340cec7432d 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java @@ -857,7 +857,7 @@ public class CameraTestUtils extends Assert { // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. // Same goes for DEPTH_POINT_CLOUD if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD || - format == ImageFormat.RAW_PRIVATE) { + format == ImageFormat.DEPTH_JPEG || format == ImageFormat.RAW_PRIVATE) { buffer = planes[0].getBuffer(); assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer); data = new byte[buffer.remaining()]; @@ -940,6 +940,7 @@ public class CameraTestUtils extends Assert { case ImageFormat.RAW_PRIVATE: case ImageFormat.DEPTH16: case ImageFormat.DEPTH_POINT_CLOUD: + case ImageFormat.DEPTH_JPEG: assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length); break; default: @@ -1363,6 +1364,9 @@ public class CameraTestUtils extends Assert { case ImageFormat.RAW_PRIVATE: validateRawPrivateData(data, width, height, image.getTimestamp(), filePath); break; + case ImageFormat.DEPTH_JPEG: + validateDepthJpegData(data, width, height, format, image.getTimestamp(), filePath); + break; default: throw new UnsupportedOperationException("Unsupported format for validation: " + format); @@ -1528,6 +1532,23 @@ public class CameraTestUtils extends Assert { } + private static void validateDepthJpegData(byte[] depthData, int width, int height, int format, + long ts, String filePath) { + + if (VERBOSE) Log.v(TAG, "Validating depth jpeg data"); + + // Can't validate size since it is variable + + if (DEBUG && filePath != null) { + String fileName = + filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".jpg"; + dumpFile(fileName, depthData); + } + + return; + + } + public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) { if (result == null) { throw new IllegalArgumentException("Result must not be null"); diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 5fae9d5a7974..3156732ef02b 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -47,32 +47,13 @@ using Transaction = SurfaceComposerClient::Transaction; 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); + bool isWideColorDisplay = false; + status_t err = client->isWideColorDisplay(display, &isWideColorDisplay); 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; + return isWideColorDisplay; } static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) { diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 83084c519f19..7eaf04bfb775 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -17,7 +17,6 @@ package com.android.captiveportallogin; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; -import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME; import android.app.Activity; import android.app.AlertDialog; @@ -40,8 +39,6 @@ import android.net.http.SslError; import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,16 +58,17 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import java.lang.InterruptedException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; @@ -81,6 +79,7 @@ public class CaptivePortalLoginActivity extends Activity { private static final boolean VDBG = false; private static final int SOCKET_TIMEOUT_MS = 10000; + public static final String HTTP_LOCATION_HEADER_NAME = "Location"; private enum Result { DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED), diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index f346b00f85b8..ce58d4ea84aa 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -230,10 +230,10 @@ public class Assistant extends NotificationAssistantService { Bundle signals = new Bundle(); if (!smartActions.isEmpty()) { - signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions); + signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, smartActions); } if (!smartReplies.isEmpty()) { - signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies); + signals.putCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES, smartReplies); } if (mSettings.mNewInterruptionModel) { if (mNotificationCategorizer.shouldSilence(entry)) { diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index a2da0a079550..9b0d896e483e 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -21,6 +21,7 @@ java_library { installable: true, srcs: [ "src/**/*.java", + ":framework-networkstack-shared-srcs", ":services-networkstack-shared-srcs", ], static_libs: [ diff --git a/packages/NetworkStack/TEST_MAPPING b/packages/NetworkStack/TEST_MAPPING new file mode 100644 index 000000000000..55ba5916b7d8 --- /dev/null +++ b/packages/NetworkStack/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "NetworkStackTests" + } + ] +}
\ No newline at end of file diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 50c4dfc8d700..08452bbbe433 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -26,11 +26,6 @@ import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_RAW; -import static com.android.internal.util.BitUtils.bytesToBEInt; -import static com.android.internal.util.BitUtils.getUint16; -import static com.android.internal.util.BitUtils.getUint32; -import static com.android.internal.util.BitUtils.getUint8; -import static com.android.internal.util.BitUtils.uint32; import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; @@ -1586,6 +1581,29 @@ public class ApfFilter { // TODO: move to android.net.NetworkUtils @VisibleForTesting public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) { - return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength); + return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength); + } + + private static int uint8(byte b) { + return b & 0xff; + } + + private static int getUint16(ByteBuffer buffer, int position) { + return buffer.getShort(position) & 0xffff; + } + + private static long getUint32(ByteBuffer buffer, int position) { + return Integer.toUnsignedLong(buffer.getInt(position)); + } + + private static int getUint8(ByteBuffer buffer, int position) { + return uint8(buffer.get(position)); + } + + private static int bytesToBEInt(byte[] bytes) { + return (uint8(bytes[0]) << 24) + + (uint8(bytes[1]) << 16) + + (uint8(bytes[2]) << 8) + + (uint8(bytes[3])); } } diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index 04ac9a301813..12eecc070a06 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -40,6 +40,8 @@ import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_RCVBUF; import static android.system.OsConstants.SO_REUSEADDR; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import android.content.Context; import android.net.DhcpResults; import android.net.NetworkUtils; @@ -328,7 +330,7 @@ public class DhcpClient extends StateMachine { Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); - Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); + Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error creating UDP socket", e); return false; diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java index 0d298de4f5f8..0a15cd7d3bd9 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java @@ -16,12 +16,13 @@ package android.net.dhcp; -import static android.net.NetworkUtils.inet4AddressToIntHTH; -import static android.net.NetworkUtils.intToInet4AddressHTH; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; import static android.net.dhcp.DhcpLease.inet4AddrToString; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS; import static java.lang.Math.min; @@ -201,7 +202,7 @@ class DhcpLeaseRepository { private static boolean isIpAddrOutsidePrefix(@NonNull IpPrefix prefix, @Nullable Inet4Address addr) { - return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr); + return addr != null && !addr.equals(IPV4_ADDR_ANY) && !prefix.contains(addr); } @Nullable diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java index ce8b7e78d0f8..d7ff98b1f501 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java @@ -1,10 +1,13 @@ package android.net.dhcp; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; -import android.net.NetworkUtils; import android.net.metrics.DhcpErrorEvent; +import android.net.shared.Inet4AddressUtils; import android.os.Build; import android.os.SystemProperties; import android.system.OsConstants; @@ -43,8 +46,8 @@ public abstract class DhcpPacket { public static final int MINIMUM_LEASE = 60; public static final int INFINITE_LEASE = (int) 0xffffffff; - public static final Inet4Address INADDR_ANY = (Inet4Address) Inet4Address.ANY; - public static final Inet4Address INADDR_BROADCAST = (Inet4Address) Inet4Address.ALL; + public static final Inet4Address INADDR_ANY = IPV4_ADDR_ANY; + public static final Inet4Address INADDR_BROADCAST = IPV4_ADDR_ALL; public static final byte[] ETHER_BROADCAST = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, @@ -1212,9 +1215,9 @@ public abstract class DhcpPacket { */ public DhcpResults toDhcpResults() { Inet4Address ipAddress = mYourIp; - if (ipAddress.equals(Inet4Address.ANY)) { + if (ipAddress.equals(IPV4_ADDR_ANY)) { ipAddress = mClientIp; - if (ipAddress.equals(Inet4Address.ANY)) { + if (ipAddress.equals(IPV4_ADDR_ANY)) { return null; } } @@ -1222,13 +1225,13 @@ public abstract class DhcpPacket { int prefixLength; if (mSubnetMask != null) { try { - prefixLength = NetworkUtils.netmaskToPrefixLength(mSubnetMask); + prefixLength = Inet4AddressUtils.netmaskToPrefixLength(mSubnetMask); } catch (IllegalArgumentException e) { // Non-contiguous netmask. return null; } } else { - prefixLength = NetworkUtils.getImplicitNetmask(ipAddress); + prefixLength = Inet4AddressUtils.getImplicitNetmask(ipAddress); } DhcpResults results = new DhcpResults(); diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java index 7b112dfc125c..beabd3eb3152 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java @@ -16,8 +16,6 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; @@ -25,6 +23,8 @@ import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; @@ -33,6 +33,8 @@ import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; import static java.lang.Integer.toUnsignedLong; @@ -434,7 +436,7 @@ public class DhcpServer extends IDhcpServer.Stub { if (!isEmpty(request.mRelayIp)) { return request.mRelayIp; } else if (broadcastFlag) { - return (Inet4Address) Inet4Address.ALL; + return IPV4_ADDR_ALL; } else if (!isEmpty(request.mClientIp)) { return request.mClientIp; } else { @@ -517,7 +519,7 @@ public class DhcpServer extends IDhcpServer.Stub { request.mRelayIp, request.mClientMac, true /* broadcast */, message); final Inet4Address dst = isEmpty(request.mRelayIp) - ? (Inet4Address) Inet4Address.ALL + ? IPV4_ADDR_ALL : request.mRelayIp; return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst); } @@ -598,7 +600,7 @@ public class DhcpServer extends IDhcpServer.Stub { } private static boolean isEmpty(@Nullable Inet4Address address) { - return address == null || Inet4Address.ANY.equals(address); + return address == null || IPV4_ADDR_ANY.equals(address); } private class PacketListener extends DhcpPacketListener { @@ -632,7 +634,7 @@ public class DhcpServer extends IDhcpServer.Stub { SocketUtils.bindSocketToInterface(mSocket, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); - Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER); + Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER); return mSocket; } catch (IOException | ErrnoException e) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java index 868f3bed4eed..31ce95b11ba9 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java @@ -16,8 +16,8 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; -import static android.net.NetworkUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU; @@ -29,7 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.IpPrefix; import android.net.LinkAddress; -import android.net.NetworkUtils; +import android.net.shared.Inet4AddressUtils; import android.util.ArraySet; import java.net.Inet4Address; @@ -164,7 +164,8 @@ public class DhcpServingParams { */ @NonNull public Inet4Address getBroadcastAddress() { - return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength()); + return Inet4AddressUtils.getBroadcastAddress( + getServerInet4Addr(), serverAddr.getPrefixLength()); } /** diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index ad7f85d0a30a..f20e01636d72 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -29,7 +29,6 @@ import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Network; import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.ProxyInfoParcelable; @@ -1000,7 +999,9 @@ public class IpClient extends StateMachine { // mDhcpResults is never shared with any other owner so we don't have // to worry about concurrent modification. if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { + final List<RouteInfo> routes = + mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName); + for (RouteInfo route : routes) { newLp.addRoute(route); } addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index eb993a4243a9..2e6ff243a628 100644 --- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java @@ -36,8 +36,6 @@ import android.system.Os; import android.system.OsConstants; import android.util.Log; -import com.android.internal.util.BitUtils; - import libcore.io.IoUtils; import java.io.FileDescriptor; @@ -186,7 +184,7 @@ public class IpNeighborMonitor extends PacketReader { final int srcPortId = nlMsg.getHeader().nlmsg_pid; if (srcPortId != 0) { - mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId)); + mLog.e("non-kernel source portId: " + Integer.toUnsignedLong(srcPortId)); break; } diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java index 761db6822fb4..76a03387a12d 100644 --- a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java @@ -31,15 +31,15 @@ import android.net.metrics.IpReachabilityEvent; import android.net.netlink.StructNdMsg; import android.net.util.InterfaceParams; import android.net.util.SharedLog; +import android.os.ConditionVariable; import android.os.Handler; +import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.DumpUtils.Dump; import java.io.PrintWriter; import java.net.Inet6Address; @@ -215,15 +215,20 @@ public class IpReachabilityMonitor { } public void dump(PrintWriter pw) { - DumpUtils.dumpAsync( - mIpNeighborMonitor.getHandler(), - new Dump() { - @Override - public void dump(PrintWriter pw, String prefix) { - pw.println(describeWatchList("\n")); - } - }, - pw, "", 1000); + if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) { + pw.println(describeWatchList("\n")); + return; + } + + final ConditionVariable cv = new ConditionVariable(false); + mIpNeighborMonitor.getHandler().post(() -> { + pw.println(describeWatchList("\n")); + cv.open(); + }); + + if (!cv.block(1000)) { + pw.println("Timed out waiting for IpReachabilityMonitor dump"); + } } private String describeWatchList() { return describeWatchList(" "); } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index cddb91f65d0f..6dcf0c0d1626 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package android.net.dhcp; +package android.net.util; /** - * TODO: remove this class after migrating clients. + * Collection of utilities for the network stack. */ -public class DhcpClient { - public static final int CMD_PRE_DHCP_ACTION = 1003; - public static final int CMD_POST_DHCP_ACTION = 1004; - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006; +public class NetworkStackUtils { - public static final int DHCP_SUCCESS = 1; - public static final int DHCP_FAILURE = 2; + /** + * @return True if the array is null or 0-length. + */ + public static <T> boolean isEmpty(T[] array) { + return array == null || array.length == 0; + } } diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserver.java b/packages/NetworkStack/src/com/android/server/NetworkObserver.java new file mode 100644 index 000000000000..d3b40a67633d --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/NetworkObserver.java @@ -0,0 +1,87 @@ +/* + * 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; + +import android.net.LinkAddress; + +/** + * Observer for network events, to use with {@link NetworkObserverRegistry}. + */ +public interface NetworkObserver { + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceChanged(java.lang.String, boolean) + */ + default void onInterfaceChanged(String ifName, boolean up) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceRemoved(String) + */ + default void onInterfaceRemoved(String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceAddressUpdated(String, String, int, int) + */ + default void onInterfaceAddressUpdated(LinkAddress address, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceAddressRemoved(String, String, int, int) + */ + default void onInterfaceAddressRemoved(LinkAddress address, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceLinkStateChanged(String, boolean) + */ + default void onInterfaceLinkStateChanged(String ifName, boolean up) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onInterfaceAdded(String) + */ + default void onInterfaceAdded(String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceClassActivityChanged(boolean, int, long, int) + */ + default void onInterfaceClassActivityChanged( + boolean isActive, int label, long timestamp, int uid) {} + + /** + * @see android.net.INetdUnsolicitedEventListener#onQuotaLimitReached(String, String) + */ + default void onQuotaLimitReached(String alertName, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onInterfaceDnsServerInfo(String, long, String[]) + */ + default void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onRouteChanged(boolean, String, String, String) + */ + default void onRouteUpdated(String route, String gateway, String ifName) {} + + /** + * @see android.net.INetdUnsolicitedEventListener + * #onRouteChanged(boolean, String, String, String) + */ + default void onRouteRemoved(String route, String gateway, String ifName) {} +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java new file mode 100644 index 000000000000..14e6c5fdadb9 --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java @@ -0,0 +1,150 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.LinkAddress; +import android.os.Handler; +import android.os.RemoteException; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A class for reporting network events to clients. + * + * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to + * all INetworkManagementEventObserver objects that have registered with it. + */ +public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { + + /** + * Constructs a new NetworkObserverRegistry. + * + * <p>Only one registry should be used per process since netd will silently ignore multiple + * registrations from the same process. + */ + NetworkObserverRegistry() {} + + /** + * Start listening for Netd events. + * + * <p>This should be called before allowing any observer to be registered. + */ + void register(@NonNull INetd netd) throws RemoteException { + netd.registerUnsolicitedEventListener(this); + } + + private final ConcurrentHashMap<NetworkObserver, Handler> mObservers = + new ConcurrentHashMap<>(); + + /** + * Registers the specified observer and start sending callbacks to it. + * This method may be called on any thread. + */ + public void registerObserver(@NonNull NetworkObserver observer, @NonNull Handler handler) { + mObservers.put(observer, handler); + } + + /** + * Unregisters the specified observer and stop sending callbacks to it. + * This method may be called on any thread. + */ + public void unregisterObserver(@NonNull NetworkObserver observer) { + mObservers.remove(observer); + } + + @FunctionalInterface + private interface NetworkObserverEventCallback { + void sendCallback(NetworkObserver o); + } + + private void invokeForAllObservers(@NonNull final NetworkObserverEventCallback callback) { + // ConcurrentHashMap#entrySet is weakly consistent: observers that were in the map before + // creation will be processed, those added during traversal may or may not. + for (Map.Entry<NetworkObserver, Handler> entry : mObservers.entrySet()) { + final NetworkObserver observer = entry.getKey(); + entry.getValue().post(() -> callback.sendCallback(observer)); + } + } + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) { + invokeForAllObservers(o -> o.onInterfaceClassActivityChanged( + isActive, label, timestamp, uid)); + } + + /** + * Notify our observers of a limit reached. + */ + @Override + public void onQuotaLimitReached(String alertName, String ifName) { + invokeForAllObservers(o -> o.onQuotaLimitReached(alertName, ifName)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) { + invokeForAllObservers(o -> o.onInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) { + final LinkAddress address = new LinkAddress(addr, flags, scope); + invokeForAllObservers(o -> o.onInterfaceAddressUpdated(address, ifName)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) { + final LinkAddress address = new LinkAddress(addr, flags, scope); + invokeForAllObservers(o -> o.onInterfaceAddressRemoved(address, ifName)); + } + + @Override + public void onInterfaceAdded(String ifName) { + invokeForAllObservers(o -> o.onInterfaceAdded(ifName)); + } + + @Override + public void onInterfaceRemoved(String ifName) { + invokeForAllObservers(o -> o.onInterfaceRemoved(ifName)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) { + invokeForAllObservers(o -> o.onInterfaceChanged(ifName, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) { + invokeForAllObservers(o -> o.onInterfaceLinkStateChanged(ifName, up)); + } + + @Override + public void onRouteChanged(boolean updated, String route, String gateway, String ifName) { + if (updated) { + invokeForAllObservers(o -> o.onRouteUpdated(route, gateway, ifName)); + } else { + invokeForAllObservers(o -> o.onRouteRemoved(route, gateway, ifName)); + } + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) {} +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 4080ddf9e73f..631ee453671a 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -29,6 +29,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; @@ -65,6 +66,7 @@ import java.util.Iterator; */ public class NetworkStackService extends Service { private static final String TAG = NetworkStackService.class.getSimpleName(); + private static NetworkStackConnector sConnector; /** * Create a binder connector for the system server to communicate with the network stack. @@ -72,8 +74,11 @@ public class NetworkStackService extends Service { * <p>On platforms where the network stack runs in the system server process, this method may * be called directly instead of obtaining the connector by binding to the service. */ - public static IBinder makeConnector(Context context) { - return new NetworkStackConnector(context); + public static synchronized IBinder makeConnector(Context context) { + if (sConnector == null) { + sConnector = new NetworkStackConnector(context); + } + return sConnector; } @NonNull @@ -85,6 +90,8 @@ public class NetworkStackService extends Service { private static class NetworkStackConnector extends INetworkStackConnector.Stub { private static final int NUM_VALIDATION_LOG_LINES = 20; private final Context mContext; + private final INetd mNetd; + private final NetworkObserverRegistry mObserverRegistry; private final ConnectivityManager mCm; @GuardedBy("mIpClients") private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); @@ -106,7 +113,11 @@ public class NetworkStackService extends Service { NetworkStackConnector(Context context) { mContext = context; + mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE); + mObserverRegistry = new NetworkObserverRegistry(); mCm = context.getSystemService(ConnectivityManager.class); + + // TODO: call mObserverRegistry here after adding sepolicy changes } @NonNull diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 6b31b82ec3cc..96eaa505389d 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -31,6 +31,7 @@ import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; +import static android.net.util.NetworkStackUtils.isEmpty; import android.annotation.Nullable; import android.app.PendingIntent; @@ -80,7 +81,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -93,6 +93,7 @@ import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -1146,7 +1147,10 @@ public class NetworkMonitor extends StateMachine { return null; } - return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue); + final Collection<CaptivePortalProbeSpec> specs = + CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue); + final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()]; + return specs.toArray(specsArray); } catch (Exception e) { // Don't let a misconfiguration bootloop the system. Log.e(TAG, "Error parsing configured fallback probe specs", e); @@ -1169,7 +1173,7 @@ public class NetworkMonitor extends StateMachine { } private CaptivePortalProbeSpec nextFallbackSpec() { - if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) { + if (isEmpty(mCaptivePortalFallbackSpecs)) { return null; } // Randomly change spec without memory. Also randomize the first attempt. diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java index eedaf30c055f..804765e33a87 100644 --- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -16,6 +16,10 @@ package com.android.server.util; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; + +import java.net.Inet4Address; + /** * Network constants used by the network stack. */ @@ -79,6 +83,8 @@ public final class NetworkStackConstants { public static final int IPV4_SRC_ADDR_OFFSET = 12; public static final int IPV4_DST_ADDR_OFFSET = 16; public static final int IPV4_ADDR_LEN = 4; + public static final Inet4Address IPV4_ADDR_ALL = intToInet4AddressHTH(0xffffffff); + public static final Inet4Address IPV4_ADDR_ANY = intToInet4AddressHTH(0x0); /** * IPv6 constants. diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java index 51d50d9eb13a..4abd77e9cfde 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -21,6 +21,8 @@ import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -55,7 +57,6 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class DhcpLeaseRepositoryTest { - private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY; private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247"); private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241"); private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243"); @@ -108,7 +109,7 @@ public class DhcpLeaseRepositoryTest { MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); final String hostname = "host_" + i; final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); assertNotNull(lease); assertEquals(newMac, lease.getHwAddr()); @@ -130,7 +131,7 @@ public class DhcpLeaseRepositoryTest { try { mRepo.getOffer(null, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Should be out of addresses"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { // Expected @@ -181,11 +182,11 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_StableAddress() throws Exception { for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Same lease is offered twice final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease, newLease); } } @@ -196,7 +197,7 @@ public class DhcpLeaseRepositoryTest { mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS); DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertTrue(newPrefix.contains(lease.getNetAddr())); } @@ -205,7 +206,7 @@ public class DhcpLeaseRepositoryTest { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1); DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -213,12 +214,13 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_ClientIdHasExistingLease() throws Exception { final byte[] clientId = new byte[] { 1, 2 }; - mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY /* clientAddr */, - INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); + mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, + IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, + TEST_HOSTNAME_1); // Different MAC, but same clientId DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); } @@ -227,12 +229,13 @@ public class DhcpLeaseRepositoryTest { public void testGetOffer_DifferentClientId() throws Exception { final byte[] clientId1 = new byte[] { 1, 2 }; final byte[] clientId2 = new byte[] { 3, 4 }; - mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY /* clientAddr */, - INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1); + mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, + IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, + TEST_HOSTNAME_1); // Same MAC, different client ID DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); // Obtains a different address assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(HOSTNAME_NONE, offer.getHostname()); @@ -241,7 +244,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddress() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1); assertEquals(TEST_INETADDR_1, offer.getNetAddr()); assertEquals(TEST_HOSTNAME_1, offer.getHostname()); @@ -250,14 +253,14 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressInUse() throws Exception { requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); } @Test public void testGetOffer_RequestedAddressReserved() throws Exception { - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE); assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); } @@ -265,7 +268,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressInvalid() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @@ -273,7 +276,7 @@ public class DhcpLeaseRepositoryTest { @Test public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); - DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */, + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */, invalidAddr /* reqAddr */, HOSTNAME_NONE); assertNotEquals(invalidAddr, offer.getNetAddr()); } @@ -322,7 +325,7 @@ public class DhcpLeaseRepositoryTest { @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class) public void testRequestLease_SelectingRelayInInvalidSubnet() throws Exception { - mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* clientAddr */, + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */, parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, true /* sidSet */, HOSTNAME_NONE); } @@ -419,14 +422,14 @@ public class DhcpLeaseRepositoryTest { public void testReleaseLease_StableOffer() throws Exception { for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); requestLeaseSelecting(mac, lease.getNetAddr()); mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); // Same lease is offered after it was released final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertEquals(lease.getNetAddr(), newLease.getNetAddr()); } } @@ -434,13 +437,13 @@ public class DhcpLeaseRepositoryTest { @Test public void testMarkLeaseDeclined() throws Exception { final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); mRepo.markLeaseDeclined(lease.getNetAddr()); // Same lease is not offered again final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); } @@ -457,16 +460,16 @@ public class DhcpLeaseRepositoryTest { // Last 2 addresses: addresses marked declined should be used final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr()); final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, - INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); + IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr()); // Now out of addresses try { - mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY /* relayAddr */, + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); fail("Repository should be out of addresses and throw"); } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } @@ -480,7 +483,8 @@ public class DhcpLeaseRepositoryTest { private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet) throws DhcpLeaseRepository.DhcpLeaseException { - return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, INET4_ANY /* relayAddr */, + return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, + IPV4_ADDR_ANY /* relayAddr */, reqAddr, sidSet, hostname); } @@ -490,7 +494,7 @@ public class DhcpLeaseRepositoryTest { private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr, @Nullable String hostname) throws DhcpLeaseRepository.DhcpLeaseException { - return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, hostname, + return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname, true /* sidSet */); } @@ -507,7 +511,7 @@ public class DhcpLeaseRepositoryTest { */ private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr, @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException { - return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, + return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE, false /* sidSet */); } diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java index a592809618e6..7544e72da02e 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java @@ -16,9 +16,27 @@ package android.net.dhcp; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; -import static android.net.dhcp.DhcpPacket.*; +import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER; +import static android.net.dhcp.DhcpPacket.DHCP_MTU; +import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; +import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; +import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; +import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; +import static android.net.dhcp.DhcpPacket.ENCAP_L2; +import static android.net.dhcp.DhcpPacket.ENCAP_L3; +import static android.net.dhcp.DhcpPacket.INADDR_ANY; +import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.dhcp.DhcpPacket.ParseException; +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -30,11 +48,15 @@ import android.net.DhcpResults; import android.net.LinkAddress; import android.net.NetworkUtils; import android.net.metrics.DhcpErrorEvent; -import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.internal.util.HexDump; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.ByteArrayOutputStream; import java.net.Inet4Address; import java.nio.ByteBuffer; @@ -44,10 +66,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Random; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(AndroidJUnit4.class) @SmallTest public class DhcpPacketTest { @@ -60,9 +78,9 @@ public class DhcpPacketTest { SERVER_ADDR, PREFIX_LENGTH); private static final String HOSTNAME = "testhostname"; private static final short MTU = 1500; - // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code + // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code // doesn't use == instead of equals when comparing addresses. - private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0"); + private static final Inet4Address ANY = v4Address("0.0.0.0"); private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java index 3ca0564f24d6..1004382b3adf 100644 --- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java @@ -17,8 +17,8 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; -import static android.net.NetworkUtils.inet4AddressToIntHTH; import static android.net.dhcp.DhcpServingParams.MTU_UNSET; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -27,8 +27,8 @@ import static junit.framework.Assert.assertTrue; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.LinkAddress; -import android.net.NetworkUtils; import android.net.dhcp.DhcpServingParams.InvalidParameterException; +import android.net.shared.Inet4AddressUtils; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -200,7 +200,7 @@ public class DhcpServingParamsTest { } private static int[] toIntArray(Collection<Inet4Address> addrs) { - return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray(); + return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray(); } private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index aff6f0452533..a1aefabfc7f2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -822,6 +822,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS, GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS); + dumpSetting(s, p, + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, + GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST); p.end(locationToken); final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE); @@ -1711,6 +1714,12 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, SecureSettingsProto.Accessibility.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES); + dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, + SecureSettingsProto.Accessibility.NON_INTERACTIVE_UI_TIMEOUT_MS); + dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, + SecureSettingsProto.Accessibility.INTERACTIVE_UI_TIMEOUT_MS); p.end(accessibilityToken); dumpSetting(s, p, diff --git a/packages/SystemUI/res/drawable/smart_reply_button_background.xml b/packages/SystemUI/res/drawable/smart_reply_button_background.xml index 31119a90a788..5652b49818fc 100644 --- a/packages/SystemUI/res/drawable/smart_reply_button_background.xml +++ b/packages/SystemUI/res/drawable/smart_reply_button_background.xml @@ -25,7 +25,7 @@ android:insetRight="0dp" android:insetBottom="8dp"> <shape android:shape="rectangle"> - <corners android:radius="8dp" /> + <corners android:radius="@dimen/smart_reply_button_corner_radius" /> <stroke android:width="@dimen/smart_reply_button_stroke_width" android:color="@color/smart_reply_button_stroke" /> <solid android:color="@color/smart_reply_button_background"/> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a14259eca45e..d435c67364ba 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -888,8 +888,10 @@ <dimen name="smart_reply_button_stroke_width">1dp</dimen> <dimen name="smart_reply_button_font_size">14sp</dimen> <dimen name="smart_reply_button_line_spacing_extra">6sp</dimen> <!-- Total line height 20sp. --> - <dimen name="smart_action_button_icon_size">24dp</dimen> - <dimen name="smart_action_button_icon_padding">10dp</dimen> + <!-- Corner radius = half of min_height to create rounded sides. --> + <dimen name="smart_reply_button_corner_radius">24dp</dimen> + <dimen name="smart_action_button_icon_size">18dp</dimen> + <dimen name="smart_action_button_icon_padding">8dp</dimen> <!-- A reasonable upper bound for the height of the smart reply button. The measuring code needs to start with a guess for the maximum size. Currently two-line smart reply buttons diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 190bd7ae91a2..ffa5de8de2b7 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2305,8 +2305,8 @@ <item quantity="other"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</item> </plurals> - <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]--> - <string name="ongoing_privacy_dialog_cancel">Cancel</string> + <!-- Action for accepting the Ongoing privacy dialog [CHAR LIMIT=10]--> + <string name="ongoing_privacy_dialog_ok">Got it</string> <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=20]--> <string name="ongoing_privacy_dialog_open_settings">View details</string> @@ -2337,6 +2337,7 @@ <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item> <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other apps</item> </plurals> + <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] --> <string name="sensor_privacy_mode">Sensors off</string> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 5fdf76f6f0bc..b584f6781796 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,6 +33,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -590,9 +591,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private int getStatusBarHeight() { if (getRootWindowInsets() != null) { + WindowInsets insets = getRootWindowInsets(); return Math.max( - getRootWindowInsets().getSystemWindowInsetTop(), - getRootWindowInsets().getDisplayCutout().getSafeInsetTop()); + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index f3ca9386c312..4f870f6ceffc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -98,7 +98,9 @@ public class ExpandedAnimationController if (insets != null) { return mBubblePaddingPx + Math.max( insets.getSystemWindowInsetTop(), - insets.getDisplayCutout().getSafeInsetTop()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); } return mBubblePaddingPx; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index a113a630dfd8..0f5137618258 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -211,7 +211,9 @@ public class StackAnimationController extends - mBubblePadding + Math.max( insets.getSystemWindowInsetLeft(), - insets.getDisplayCutout().getSafeInsetLeft()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetLeft() + : 0); mAllowableStackPositionRegion.right = mLayout.getWidth() - mIndividualBubbleSize @@ -219,20 +221,26 @@ public class StackAnimationController extends - mBubblePadding - Math.max( insets.getSystemWindowInsetRight(), - insets.getDisplayCutout().getSafeInsetRight()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetRight() + : 0); mAllowableStackPositionRegion.top = mBubblePadding + Math.max( insets.getSystemWindowInsetTop(), - insets.getDisplayCutout().getSafeInsetTop()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); mAllowableStackPositionRegion.bottom = mLayout.getHeight() - mIndividualBubbleSize - mBubblePadding - Math.max( insets.getSystemWindowInsetBottom(), - insets.getDisplayCutout().getSafeInsetBottom()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() + : 0); } return mAllowableStackPositionRegion; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index f14495bb959d..4527f73ab118 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -40,6 +40,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricSourceType; +import android.media.AudioAttributes; import android.media.AudioManager; import android.media.SoundPool; import android.os.Bundle; @@ -750,7 +751,14 @@ public class KeyguardViewMediator extends SystemUI { mDeviceInteractive = mPM.isInteractive(); - mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); + mLockSounds = new SoundPool.Builder() + .setMaxStreams(1) + .setAudioAttributes( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build()) + .build(); String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND); if (soundPath != null) { mLockSoundId = mLockSounds.load(soundPath, 1); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 26c6d501f2cb..db5c244d95b2 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.res.ColorStateList +import android.os.UserHandle import android.util.IconDrawableFactory import android.view.Gravity import android.view.LayoutInflater @@ -48,6 +49,7 @@ class OngoingPrivacyDialog constructor( R.dimen.ongoing_appops_dialog_icon_margin) private val MAX_ITEMS = context.resources.getInteger(R.integer.ongoing_appops_dialog_max_apps) private val iconFactory = IconDrawableFactory.newInstance(context, true) + private var dismissDialog: (() -> Unit)? = null init { val a = context.theme.obtainStyledAttributes( @@ -58,8 +60,8 @@ class OngoingPrivacyDialog constructor( fun createDialog(): Dialog { val builder = AlertDialog.Builder(context).apply { - setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null) - setPositiveButton(R.string.ongoing_privacy_dialog_open_settings, + setPositiveButton(R.string.ongoing_privacy_dialog_ok, null) + setNeutralButton(R.string.ongoing_privacy_dialog_open_settings, object : DialogInterface.OnClickListener { val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra( Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1)) @@ -72,7 +74,9 @@ class OngoingPrivacyDialog constructor( }) } builder.setView(getContentView()) - return builder.create() + val dialog = builder.create() + dismissDialog = dialog::dismiss + return dialog } fun getContentView(): View { @@ -116,6 +120,7 @@ class OngoingPrivacyDialog constructor( return contentView } + @Suppress("DEPRECATION") private fun addAppItem( itemList: LinearLayout, app: PrivacyApplication, @@ -152,6 +157,16 @@ class OngoingPrivacyDialog constructor( } else { icons.visibility = View.GONE } + item.setOnClickListener(object : View.OnClickListener { + val intent = Intent(Intent.ACTION_REVIEW_APP_PERMISSION_USAGE) + .putExtra(Intent.EXTRA_PACKAGE_NAME, app.packageName) + .putExtra(Intent.EXTRA_USER, UserHandle.getUserHandleForUid(app.uid)) + override fun onClick(v: View?) { + Dependency.get(ActivityStarter::class.java) + .postStartActivityDismissingKeyguard(intent, 0) + dismissDialog?.invoke() + } + }) itemList.addView(item) } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 9252167d02bd..dbe87d1d53c6 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -37,7 +37,7 @@ data class PrivacyItem( val application: PrivacyApplication ) -data class PrivacyApplication(val packageName: String, val context: Context) +data class PrivacyApplication(val packageName: String, val uid: Int, val context: Context) : Comparable<PrivacyApplication> { override fun compareTo(other: PrivacyApplication): Int { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index 2339fae2d1ee..f1c3bf299eea 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -59,7 +59,8 @@ class PrivacyItemController @Inject constructor(val context: Context) { @Suppress("DEPRECATION") private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER) private var listening = false - val systemApp = PrivacyApplication(context.getString(R.string.device_services), context) + val systemApp = + PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context) private val callbacks = mutableListOf<WeakReference<Callback>>() private val notifyChanges = Runnable { @@ -162,7 +163,7 @@ class PrivacyItemController @Inject constructor(val context: Context) { else -> return null } if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp) - val app = PrivacyApplication(appOpItem.packageName, context) + val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context) return PrivacyItem(type, app) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 904478efb568..015085205460 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -111,6 +111,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; + private static final int MSG_DISPLAY_READY = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -274,6 +275,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } + + /** + * @see IStatusBar#onDisplayReady(int) + */ + default void onDisplayReady(int displayId) { } } @VisibleForTesting @@ -761,6 +767,13 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } + @Override + public void onDisplayReady(int displayId) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1015,6 +1028,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).showPinningEscapeToast(); } break; + case MSG_DISPLAY_READY: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onDisplayReady(msg.arg1); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 9740d1dd6d1c..e11ec2d24e29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; +import static com.android.systemui.SysUiServiceProvider.getComponent; import android.content.Context; import android.hardware.display.DisplayManager; @@ -34,6 +35,7 @@ import android.view.WindowManagerGlobal; import com.android.systemui.Dependency; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; @@ -48,7 +50,7 @@ import javax.inject.Singleton; /** A controller to handle navigation bars. */ @Singleton -public class NavigationBarController implements DisplayListener { +public class NavigationBarController implements DisplayListener, Callbacks { private static final String TAG = NavigationBarController.class.getName(); @@ -65,13 +67,11 @@ public class NavigationBarController implements DisplayListener { mHandler = handler; mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); mDisplayManager.registerDisplayListener(this, mHandler); + getComponent(mContext, CommandQueue.class).addCallback(this); } @Override - public void onDisplayAdded(int displayId) { - Display display = mDisplayManager.getDisplay(displayId); - createNavigationBar(display); - } + public void onDisplayAdded(int displayId) { } @Override public void onDisplayRemoved(int displayId) { @@ -79,7 +79,12 @@ public class NavigationBarController implements DisplayListener { } @Override - public void onDisplayChanged(int displayId) { + public void onDisplayChanged(int displayId) { } + + @Override + public void onDisplayReady(int displayId) { + Display display = mDisplayManager.getDisplay(displayId); + createNavigationBar(display); } // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to 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 35b7ef42177d..5e5241910e7f 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 @@ -39,7 +39,6 @@ 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; @@ -221,6 +220,11 @@ public class NotificationLogger implements StateListener { } @Override + public void onEntryReinflated(NotificationEntry entry) { + mExpansionStateLogger.onEntryReinflated(entry.key); + } + + @Override public void onInflationError( StatusBarNotification notification, Exception exception) { @@ -468,6 +472,13 @@ public class NotificationLogger implements StateListener { mLoggedExpansionState.remove(key); } + @VisibleForTesting + void onEntryReinflated(String key) { + // When the notification is updated, we should consider the notification as not + // yet logged. + mLoggedExpansionState.remove(key); + } + private State getState(String key) { State state = mExpansionStates.get(key); if (state == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index c10837165c0f..711b08e45117 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -448,6 +448,11 @@ public class NotificationPanelView extends PanelView implements false); addView(mKeyguardStatusView, index); + // Re-associate the clock container with the keyguard clock switch. + mBigClockContainer.removeAllViews(); + KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container); + keyguardClockSwitch.setBigClockContainer(mBigClockContainer); + // Update keyguard bottom area index = indexOfChild(mKeyguardBottomArea); removeView(mKeyguardBottomArea); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index c4f027f2abea..07c6587c56e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -479,13 +479,20 @@ public class SmartReplyView extends ViewGroup { remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal, accumulatedMeasures.mMaxChildHeight); + int buttonHeight = Math.max(getSuggestedMinimumHeight(), mPaddingTop + + accumulatedMeasures.mMaxChildHeight + mPaddingBottom); + + // Set the corner radius to half the button height to make the side of the buttons look like + // a semicircle. + for (View smartSuggestionButton : smartSuggestions) { + setCornerRadius((Button) smartSuggestionButton, ((float) buttonHeight) / 2); + } + setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), accumulatedMeasures.mMeasuredWidth), widthMeasureSpec), - resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop - + accumulatedMeasures.mMaxChildHeight + mPaddingBottom), - heightMeasureSpec)); + resolveSize(buttonHeight, heightMeasureSpec)); } /** @@ -806,6 +813,23 @@ public class SmartReplyView extends ViewGroup { button.setTextColor(textColor); } + private void setCornerRadius(Button button, float radius) { + Drawable drawable = button.getBackground(); + if (drawable instanceof RippleDrawable) { + // Mutate in case other notifications are using this drawable. + drawable = drawable.mutate(); + RippleDrawable ripple = (RippleDrawable) drawable; + Drawable inset = ripple.getDrawable(0); + if (inset instanceof InsetDrawable) { + Drawable background = ((InsetDrawable) inset).getDrawable(); + if (background instanceof GradientDrawable) { + GradientDrawable gradientDrawable = (GradientDrawable) background; + gradientDrawable.setCornerRadius(radius); + } + } + } + } + private ActivityStarter getActivityStarter() { if (mActivityStarter == null) { mActivityStarter = Dependency.get(ActivityStarter.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 0f686df87ca5..db819d57417b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -30,6 +30,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Spy; @@ -89,6 +90,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase } @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") public void testFlingSideways() throws InterruptedException { // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top @@ -119,6 +121,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase } @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") public void testFlingUpFromBelowBottomCenter() throws InterruptedException { // Move to the center of the screen, just past the bottom. mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt index d3b3dae2c1cd..f163b88357cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt @@ -27,16 +27,20 @@ import org.junit.runner.RunWith @SmallTest class PrivacyDialogBuilderTest : SysuiTestCase() { + companion object { + val TEST_UID = 1 + } + @Test fun testGenerateAppsList() { val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Bar", context)) + "Bar", TEST_UID, context)) val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication( - "Bar", context)) + "Bar", TEST_UID, context)) val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Foo", context)) + "Foo", TEST_UID, context)) val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Baz", context)) + "Baz", TEST_UID, context)) val items = listOf(bar2, foo0, baz1, bar3) @@ -55,11 +59,14 @@ class PrivacyDialogBuilderTest : SysuiTestCase() { @Test fun testOrder() { // We want location to always go last, so it will go in the "+ other apps" - val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("Camera", context)) + val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA, + PrivacyApplication("Camera", TEST_UID, context)) val appMicrophone = - PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication("Microphone", context)) + PrivacyItem(PrivacyType.TYPE_MICROPHONE, + PrivacyApplication("Microphone", TEST_UID, context)) val appLocation = - PrivacyItem(PrivacyType.TYPE_LOCATION, PrivacyApplication("Location", context)) + PrivacyItem(PrivacyType.TYPE_LOCATION, + PrivacyApplication("Location", TEST_UID, context)) val items = listOf(appLocation, appMicrophone, appCamera) val textBuilder = PrivacyDialogBuilder(context, items) 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 5ff9d1490f3a..f1d900332607 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 @@ -15,10 +15,10 @@ */ package com.android.systemui.statusbar.notification.logging; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.RemoteException; @@ -31,6 +31,7 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; +import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import org.junit.Before; import org.junit.Test; @@ -155,6 +156,27 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum()); } + @Test + public void testOnEntryReinflated() throws RemoteException { + 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); + + mLogger.onEntryReinflated(NOTIFICATION_KEY); + mLogger.onVisibilityChanged( + Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)), + Collections.emptyList()); + waitForUiOffloadThread(); + // onNotificationExpansionChanged is called the second time. + verify(mBarService, times(2)).onNotificationExpansionChanged( + NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN); + } + private NotificationVisibility createNotificationVisibility(String key, boolean visibility) { return createNotificationVisibility(key, visibility, NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java index 73f3b43aad62..6177344a3ef4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java @@ -51,6 +51,7 @@ public class NavigationBarButtonTest extends SysuiTestCase { @Before public void setup() { + mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); final Display display = createVirtualDisplay(); final SysuiTestableContext context = (SysuiTestableContext) mContext.createDisplayContext(display); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 844096d9d717..6108aaa860d5 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -177,12 +177,13 @@ public final class ContentCaptureManagerService extends final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override - public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken, + public void startSession(@NonNull IBinder activityToken, @NonNull ComponentName componentName, @NonNull String sessionId, int flags, @NonNull IResultReceiver result) { Preconditions.checkNotNull(activityToken); Preconditions.checkNotNull(componentName); Preconditions.checkNotNull(sessionId); + final int userId = UserHandle.getCallingUserId(); // TODO(b/111276913): refactor getTaskIdForActivity() to also return ComponentName, // so we don't pass it on startSession (same for Autofill) @@ -199,8 +200,9 @@ public final class ContentCaptureManagerService extends } @Override - public void finishSession(@UserIdInt int userId, @NonNull String sessionId) { + public void finishSession(@NonNull String sessionId) { Preconditions.checkNotNull(sessionId); + final int userId = UserHandle.getCallingUserId(); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); @@ -209,15 +211,16 @@ public final class ContentCaptureManagerService extends } @Override - public void getReceiverServiceComponentName(@UserIdInt int userId, - IResultReceiver receiver) { + public void getServiceComponentName(@NonNull IResultReceiver result) { + final int userId = UserHandle.getCallingUserId(); ComponentName connectedServiceComponentName; synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); connectedServiceComponentName = service.getServiceComponentName(); } try { - receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName)); + result.send(/* resultCode= */ 0, + SyncResultReceiver.bundleFor(connectedServiceComponentName)); } catch (RemoteException e) { // Ignore exception as we need to be resilient against app behavior. Slog.w(TAG, "Unable to send service component name: " + e); @@ -225,8 +228,9 @@ public final class ContentCaptureManagerService extends } @Override - public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) { + public void removeUserData(@NonNull UserDataRemovalRequest request) { Preconditions.checkNotNull(request); + final int userId = UserHandle.getCallingUserId(); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.removeUserDataLocked(request); diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index dbff957a0da7..275661084aa3 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -17,5 +17,15 @@ package com.android.server; public interface AlarmManagerInternal { - void removeAlarmsForUid(int uid); + // Some other components in the system server need to know about + // broadcast alarms currently in flight + public interface InFlightListener { + /** There is now an alarm pending delivery to the given app */ + void broadcastAlarmPending(int recipientUid); + /** A broadcast alarm targeted to the given app has completed delivery */ + void broadcastAlarmComplete(int recipientUid); + } + + public void removeAlarmsForUid(int uid); + public void registerInFlightListener(InFlightListener callback); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index e3dcb7d331cf..10b532700d1b 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -197,6 +197,8 @@ class AlarmManagerService extends SystemService { PowerManager.WakeLock mWakeLock; ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>(); ArrayList<InFlight> mInFlight = new ArrayList<>(); + private final ArrayList<AlarmManagerInternal.InFlightListener> mInFlightListeners = + new ArrayList<>(); AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; @@ -1315,6 +1317,10 @@ class AlarmManagerService extends SystemService { mAlarmType = alarm.type; } + boolean isBroadcast() { + return mPendingIntent != null && mPendingIntent.isBroadcast(); + } + @Override public String toString() { return "InFlight{" @@ -1354,6 +1360,20 @@ class AlarmManagerService extends SystemService { } } + private void notifyBroadcastAlarmPendingLocked(int uid) { + final int numListeners = mInFlightListeners.size(); + for (int i = 0; i < numListeners; i++) { + mInFlightListeners.get(i).broadcastAlarmPending(uid); + } + } + + private void notifyBroadcastAlarmCompleteLocked(int uid) { + final int numListeners = mInFlightListeners.size(); + for (int i = 0; i < numListeners; i++) { + mInFlightListeners.get(i).broadcastAlarmComplete(uid); + } + } + static final class FilterStats { final BroadcastStats mBroadcastStats; final String mTag; @@ -1976,6 +1996,13 @@ class AlarmManagerService extends SystemService { removeLocked(uid); } } + + @Override + public void registerInFlightListener(InFlightListener callback) { + synchronized (mLock) { + mInFlightListeners.add(callback); + } + } } /** @@ -4426,7 +4453,11 @@ class AlarmManagerService extends SystemService { private InFlight removeLocked(PendingIntent pi, Intent intent) { for (int i = 0; i < mInFlight.size(); i++) { - if (mInFlight.get(i).mPendingIntent == pi) { + final InFlight inflight = mInFlight.get(i); + if (inflight.mPendingIntent == pi) { + if (pi.isBroadcast()) { + notifyBroadcastAlarmCompleteLocked(inflight.mUid); + } return mInFlight.remove(i); } } @@ -4649,6 +4680,9 @@ class AlarmManagerService extends SystemService { final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; + if (inflight.isBroadcast()) { + notifyBroadcastAlarmPendingLocked(alarm.uid); + } if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java index 4a8706e7090f..2f7929c962eb 100644 --- a/services/core/java/com/android/server/LooperStatsService.java +++ b/services/core/java/com/android/server/LooperStatsService.java @@ -55,11 +55,11 @@ public class LooperStatsService extends Binder { "debug.sys.looper_stats_enabled"; private static final int DEFAULT_SAMPLING_INTERVAL = 100; private static final int DEFAULT_ENTRIES_SIZE_CAP = 2000; - private static final boolean DEFAULT_ENABLED = false; + private static final boolean DEFAULT_ENABLED = true; private final Context mContext; private final LooperStats mStats; - private boolean mEnabled = false; + private boolean mEnabled = DEFAULT_ENABLED; private LooperStatsService(Context context, LooperStats stats) { this.mContext = context; diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 8adc416fda95..84577f125f1d 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -16,6 +16,9 @@ package com.android.server; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; import android.os.Environment; @@ -46,6 +49,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Retention; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -55,7 +59,8 @@ import java.util.Set; /** * Monitors the health of packages on the system and notifies interested observers when packages - * fail. All registered observers will be notified until an observer takes a mitigation action. + * fail. On failure, the registered observer with the least user impacting mitigation will + * be notified. */ public class PackageWatchdog { private static final String TAG = "PackageWatchdog"; @@ -78,7 +83,8 @@ public class PackageWatchdog { private final Context mContext; // Handler to run package cleanup runnables private final Handler mTimerHandler; - private final Handler mIoHandler; + // Handler for processing IO and observer actions + private final Handler mWorkerHandler; // Contains (observer-name -> observer-handle) that have ever been registered from // previous boots. Observers with all packages expired are periodically pruned. // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. @@ -101,7 +107,7 @@ public class PackageWatchdog { mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"), "package-watchdog.xml")); mTimerHandler = new Handler(Looper.myLooper()); - mIoHandler = BackgroundThread.getHandler(); + mWorkerHandler = BackgroundThread.getHandler(); mPackageCleanup = this::rescheduleCleanup; loadFromFile(); } @@ -115,7 +121,7 @@ public class PackageWatchdog { mContext = context; mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); mTimerHandler = new Handler(looper); - mIoHandler = mTimerHandler; + mWorkerHandler = mTimerHandler; mPackageCleanup = this::rescheduleCleanup; loadFromFile(); } @@ -228,49 +234,46 @@ public class PackageWatchdog { /** * Called when a process fails either due to a crash or ANR. * - * <p>All registered observers for the packages contained in the process will be notified in - * order of priority until an observer signifies that it has taken action and other observers - * should not notified. + * <p>For each package contained in the process, one registered observer with the least user + * impact will be notified for mitigation. * * <p>This method could be called frequently if there is a severe problem on the device. */ public void onPackageFailure(String[] packages) { - ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>(); - synchronized (mLock) { - if (mAllObservers.isEmpty()) { - return; - } + mWorkerHandler.post(() -> { + synchronized (mLock) { + if (mAllObservers.isEmpty()) { + return; + } - for (int pIndex = 0; pIndex < packages.length; pIndex++) { - // Observers interested in receiving packageName failures - List<PackageHealthObserver> observersToNotify = new ArrayList<>(); - for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { - PackageHealthObserver registeredObserver = - mAllObservers.valueAt(oIndex).mRegisteredObserver; - if (registeredObserver != null) { - observersToNotify.add(registeredObserver); + for (int pIndex = 0; pIndex < packages.length; pIndex++) { + String packageToReport = packages[pIndex]; + // Observer that will receive failure for packageToReport + PackageHealthObserver currentObserverToNotify = null; + int currentObserverImpact = Integer.MAX_VALUE; + + // Find observer with least user impact + for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { + ObserverInternal observer = mAllObservers.valueAt(oIndex); + PackageHealthObserver registeredObserver = observer.mRegisteredObserver; + if (registeredObserver != null + && observer.onPackageFailure(packageToReport)) { + int impact = registeredObserver.onHealthCheckFailed(packageToReport); + if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + && impact < currentObserverImpact) { + currentObserverToNotify = registeredObserver; + currentObserverImpact = impact; + } + } } - } - // Save interested observers and notify them outside the lock - if (!observersToNotify.isEmpty()) { - packagesToReport.put(packages[pIndex], observersToNotify); - } - } - } - // Notify observers - for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) { - List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex); - String packageName = packages[pIndex]; - for (int oIndex = 0; oIndex < observers.size(); oIndex++) { - PackageHealthObserver observer = observers.get(oIndex); - if (mAllObservers.get(observer.getName()).onPackageFailure(packageName) - && observer.onHealthCheckFailed(packageName)) { - // Observer has handled, do not notify others - break; + // Execute action with least user impact + if (currentObserverToNotify != null) { + currentObserverToNotify.execute(packageToReport); + } } } - } + }); } // TODO(zezeozue): Optimize write? Maybe only write a separate smaller file? @@ -278,21 +281,46 @@ public class PackageWatchdog { /** Writes the package information to file during shutdown. */ public void writeNow() { if (!mAllObservers.isEmpty()) { - mIoHandler.removeCallbacks(this::saveToFile); + mWorkerHandler.removeCallbacks(this::saveToFile); pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs); saveToFile(); Slog.i(TAG, "Last write to update package durations"); } } + /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ + @Retention(SOURCE) + @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, + PackageHealthObserverImpact.USER_IMPACT_LOW, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM, + PackageHealthObserverImpact.USER_IMPACT_HIGH}) + public @interface PackageHealthObserverImpact { + /** No action to take. */ + int USER_IMPACT_NONE = 0; + /* Action has low user impact, user of a device will barely notice. */ + int USER_IMPACT_LOW = 1; + /* Action has medium user impact, user of a device will likely notice. */ + int USER_IMPACT_MEDIUM = 3; + /* Action has high user impact, a last resort, user of a device will be very frustrated. */ + int USER_IMPACT_HIGH = 5; + } + /** Register instances of this interface to receive notifications on package failure. */ public interface PackageHealthObserver { /** * Called when health check fails for the {@code packageName}. - * @return {@code true} if action was taken and other observers should not be notified of - * this failure, {@code false} otherwise. + * + * @return any one of {@link PackageHealthObserverImpact} to express the impact + * to the user on {@link #execute} */ - boolean onHealthCheckFailed(String packageName); + @PackageHealthObserverImpact int onHealthCheckFailed(String packageName); + + /** + * Executes mitigation for {@link #onHealthCheckFailed}. + * + * @return {@code true} if action was executed successfully, {@code false} otherwise + */ + boolean execute(String packageName); // TODO(zezeozue): Ensure uniqueness? /** @@ -442,8 +470,8 @@ public class PackageWatchdog { } private void saveToFileAsync() { - mIoHandler.removeCallbacks(this::saveToFile); - mIoHandler.post(this::saveToFile); + mWorkerHandler.removeCallbacks(this::saveToFile); + mWorkerHandler.post(this::saveToFile); } /** @@ -606,7 +634,11 @@ public class PackageWatchdog { } else { mFailures++; } - return mFailures >= TRIGGER_FAILURE_COUNT; + boolean failed = mFailures >= TRIGGER_FAILURE_COUNT; + if (failed) { + mFailures = 0; + } + return failed; } } } diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index d09823efb6fa..a5a515f93e75 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -34,11 +34,11 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; @@ -48,6 +48,9 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; /** * Find the best Service, and bind to it. @@ -61,16 +64,20 @@ public class ServiceWatcher implements ServiceConnection { public static final String EXTRA_SERVICE_VERSION = "serviceVersion"; public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser"; + + /** Function to run on binder interface. */ + public interface BinderRunner { + /** Called to run client code with the binder. */ + void run(IBinder binder) throws RemoteException; + } + /** - * The runner that runs on the binder retrieved from {@link ServiceWatcher}. + * Function to run on binder interface. + * @param <T> Type to return. */ - public interface BinderRunner { - /** - * Runs on the retrieved binder. - * - * @param binder the binder retrieved from the {@link ServiceWatcher}. - */ - void run(IBinder binder); + public interface BlockingBinderRunner<T> { + /** Called to run client code with the binder. */ + T run(IBinder binder) throws RemoteException; } public static ArrayList<HashSet<Signature>> getSignatureSets(Context context, @@ -120,18 +127,14 @@ public class ServiceWatcher implements ServiceConnection { private final Handler mHandler; - // this lock is held to ensure the service binder is not exposed (via runOnBinder) until after - // the new service initialization work has completed - private final Object mBindLock = new Object(); - // read/write from handler thread + private IBinder mBestService; private int mCurrentUserId; // read from any thread, write from handler thread private volatile ComponentName mBestComponent; private volatile int mBestVersion; private volatile int mBestUserId; - private volatile IBinder mBestService; public ServiceWatcher(Context context, String logTag, String action, int overlaySwitchResId, int defaultServicePackageNameResId, @@ -163,17 +166,9 @@ public class ServiceWatcher implements ServiceConnection { mBestService = null; } - // called on handler thread - @GuardedBy("mBindLock") - protected void onBind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - } + protected void onBind() {} - // called on handler thread - @GuardedBy("mBindLock") - protected void onUnbind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - } + protected void onUnbind() {} /** * Start this watcher, including binding to the current best match and @@ -248,25 +243,6 @@ public class ServiceWatcher implements ServiceConnection { return bestComponent == null ? null : bestComponent.getPackageName(); } - /** - * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run - * serially. - */ - public final void runOnBinder(BinderRunner runner) { - synchronized (mBindLock) { - IBinder service = mBestService; - if (service != null) { - try { - runner.run(service); - } catch (Exception e) { - // remote exceptions cannot be allowed to crash system server - Log.e(TAG, "exception while while running " + runner + " on " + service - + " from " + this, e); - } - } - } - } - private boolean isServiceMissing() { return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction), PackageManager.MATCH_DIRECT_BOOT_AWARE @@ -380,28 +356,66 @@ public class ServiceWatcher implements ServiceConnection { mBestUserId = UserHandle.USER_NULL; } + /** + * Runs the given function asynchronously if currently connected. Suppresses any RemoteException + * thrown during execution. + */ + public final void runOnBinder(BinderRunner runner) { + runOnHandler(() -> { + if (mBestService == null) { + return; + } + + try { + runner.run(mBestService); + } catch (RuntimeException e) { + // the code being run is privileged, but may be outside the system server, and thus + // we cannot allow runtime exceptions to crash the system server + Log.e(TAG, "exception while while running " + runner + " on " + mBestService + + " from " + this, e); + } catch (RemoteException e) { + // do nothing + } + }); + } + + /** + * Runs the given function synchronously if currently connected, and returns the default value + * if not currently connected or if any exception is thrown. + */ + public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) { + try { + return runOnHandlerBlocking(() -> { + if (mBestService == null) { + return defaultValue; + } + + try { + return runner.run(mBestService); + } catch (RemoteException e) { + return defaultValue; + } + }); + } catch (InterruptedException e) { + return defaultValue; + } + } + @Override public final void onServiceConnected(ComponentName component, IBinder binder) { - mHandler.post(() -> { + runOnHandler(() -> { if (D) Log.d(mTag, component + " connected"); - - // hold the lock so that mBestService cannot be used by runOnBinder until complete - synchronized (mBindLock) { - mBestService = binder; - onBind(); - } + mBestService = binder; + onBind(); }); } @Override public final void onServiceDisconnected(ComponentName component) { - mHandler.post(() -> { + runOnHandler(() -> { if (D) Log.d(mTag, component + " disconnected"); - mBestService = null; - synchronized (mBindLock) { - onUnbind(); - } + onUnbind(); }); } @@ -410,4 +424,32 @@ public class ServiceWatcher implements ServiceConnection { ComponentName bestComponent = mBestComponent; return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion; } + + private void runOnHandler(Runnable r) { + if (Looper.myLooper() == mHandler.getLooper()) { + r.run(); + } else { + mHandler.post(r); + } + } + + private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException { + if (Looper.myLooper() == mHandler.getLooper()) { + try { + return c.call(); + } catch (Exception e) { + // Function cannot throw exception, this should never happen + throw new IllegalStateException(e); + } + } else { + FutureTask<T> task = new FutureTask<>(c); + mHandler.post(task); + try { + return task.get(); + } catch (ExecutionException e) { + // Function cannot throw exception, this should never happen + throw new IllegalStateException(e); + } + } + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index cec825fb9c00..cecd55a325d3 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -802,10 +802,7 @@ class StorageManagerService extends IStorageManager.Stub // For now, simply clone property when it changes DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE, mContext.getMainExecutor(), (namespace, name, value) -> { - if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) { - Settings.Global.putString(mResolver, - Settings.Global.ISOLATED_STORAGE_REMOTE, value); - } + refreshIsolatedStorageSettings(); }); refreshIsolatedStorageSettings(); } @@ -840,6 +837,12 @@ class StorageManagerService extends IStorageManager.Stub } private void refreshIsolatedStorageSettings() { + // Always copy value from newer DeviceConfig location + Settings.Global.putString(mResolver, + Settings.Global.ISOLATED_STORAGE_REMOTE, + DeviceConfig.getProperty(DeviceConfig.Storage.NAMESPACE, + DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED)); + final int local = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ISOLATED_STORAGE_LOCAL, 0); final int remote = Settings.Global.getInt(mContext.getContentResolver(), diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index ea6d435c7ba5..f6e698ff7839 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -33,8 +33,10 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.os.BatteryStats; import android.os.Binder; +import android.os.ExternalVibration; import android.os.Handler; import android.os.IBinder; +import android.os.IExternalVibratorService; import android.os.IVibratorService; import android.os.PowerManager; import android.os.PowerManager.ServiceType; @@ -75,16 +77,18 @@ public class VibratorService extends IVibratorService.Stub private static final String TAG = "VibratorService"; private static final boolean DEBUG = false; private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; + private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 }; - // Scale levels. Each level is defined as the delta between the current setting and the default - // intensity for that type of vibration (i.e. current - default). - private static final int SCALE_VERY_LOW = -2; - private static final int SCALE_LOW = -1; - private static final int SCALE_NONE = 0; - private static final int SCALE_HIGH = 1; - private static final int SCALE_VERY_HIGH = 2; + // Scale levels. Each level, except MUTE, is defined as the delta between the current setting + // and the default intensity for that type of vibration (i.e. current - default). + private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100 + private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2 + private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1 + private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0 + private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1 + private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2 // Gamma adjustments for scale levels. private static final float SCALE_VERY_LOW_GAMMA = 2.0f; @@ -111,6 +115,7 @@ public class VibratorService extends IVibratorService.Stub private final int mPreviousVibrationsLimit; private final boolean mAllowPriorityVibrationsInLowPowerMode; private final boolean mSupportsAmplitudeControl; + private final boolean mSupportsExternalControl; private final int mDefaultVibrationAmplitude; private final SparseArray<VibrationEffect> mFallbackEffects; private final SparseArray<Integer> mProcStatesCache = new SparseArray(); @@ -138,18 +143,20 @@ public class VibratorService extends IVibratorService.Stub @GuardedBy("mLock") private Vibration mCurrentVibration; private int mCurVibUid = -1; + private ExternalVibration mCurrentExternalVibration; + private boolean mVibratorUnderExternalControl; private boolean mLowPowerMode; private int mHapticFeedbackIntensity; private int mNotificationIntensity; private int mRingIntensity; - native static boolean vibratorExists(); - native static void vibratorInit(); - native static void vibratorOn(long milliseconds); - native static void vibratorOff(); - native static boolean vibratorSupportsAmplitudeControl(); - native static void vibratorSetAmplitude(int amplitude); - native static long vibratorPerformEffect(long effect, long strength); + static native boolean vibratorExists(); + static native void vibratorInit(); + static native void vibratorOn(long milliseconds); + static native void vibratorOff(); + static native boolean vibratorSupportsAmplitudeControl(); + static native void vibratorSetAmplitude(int amplitude); + static native long vibratorPerformEffect(long effect, long strength); static native boolean vibratorSupportsExternalControl(); static native void vibratorSetExternalControl(boolean enabled); @@ -218,6 +225,9 @@ public class VibratorService extends IVibratorService.Stub } public boolean isHapticFeedback() { + if (VibratorService.this.isHapticFeedback(usageHint)) { + return true; + } if (effect instanceof VibrationEffect.Prebaked) { VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; switch (prebaked.getId()) { @@ -239,19 +249,11 @@ public class VibratorService extends IVibratorService.Stub } public boolean isNotification() { - switch (usageHint) { - case AudioAttributes.USAGE_NOTIFICATION: - case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: - case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: - case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: - return true; - default: - return false; - } + return VibratorService.this.isNotification(usageHint); } public boolean isRingtone() { - return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE; + return VibratorService.this.isRingtone(usageHint); } public boolean isFromSystem() { @@ -332,6 +334,7 @@ public class VibratorService extends IVibratorService.Stub vibratorOff(); mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl(); + mSupportsExternalControl = vibratorSupportsExternalControl(); mContext = context; PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); @@ -379,6 +382,8 @@ public class VibratorService extends IVibratorService.Stub mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_NONE_GAMMA)); mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_HIGH_GAMMA)); mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_VERY_HIGH_GAMMA)); + + ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); } private VibrationEffect createEffectFromResource(int resId) { @@ -562,6 +567,16 @@ public class VibratorService extends IVibratorService.Stub } } + + // If something has external control of the vibrator, assume that it's more + // important for now. + if (mCurrentExternalVibration != null) { + if (DEBUG) { + Slog.d(TAG, "Ignoring incoming vibration for current external vibration"); + } + return; + } + // If the current vibration is repeating and the incoming one is non-repeating, // then ignore the non-repeating vibration. This is so that we don't cancel // vibrations that are meant to grab the attention of the user, like ringtones and @@ -648,6 +663,11 @@ public class VibratorService extends IVibratorService.Stub mThread.cancel(); mThread = null; } + if (mCurrentExternalVibration != null) { + mCurrentExternalVibration.mute(); + mCurrentExternalVibration = null; + setVibratorUnderExternalControl(false); + } doVibratorOff(); reportFinishVibrationLocked(); } finally { @@ -1095,6 +1115,26 @@ public class VibratorService extends IVibratorService.Stub } } + private static boolean isNotification(int usageHint) { + switch (usageHint) { + case AudioAttributes.USAGE_NOTIFICATION: + case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: + case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: + return true; + default: + return false; + } + } + + private static boolean isRingtone(int usageHint) { + return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE; + } + + private static boolean isHapticFeedback(int usageHint) { + return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; + } + private void noteVibratorOnLocked(int uid, long millis) { try { mBatteryStatsService.noteVibratorOn(uid, millis); @@ -1116,6 +1156,18 @@ public class VibratorService extends IVibratorService.Stub } } + private void setVibratorUnderExternalControl(boolean externalControl) { + if (DEBUG) { + if (externalControl) { + Slog.d(TAG, "Vibrator going under external control."); + } else { + Slog.d(TAG, "Taking back control of vibrator."); + } + } + mVibratorUnderExternalControl = externalControl; + vibratorSetExternalControl(externalControl); + } + private class VibrateThread extends Thread { private final VibrationEffect.Waveform mWaveform; private final int mUid; @@ -1290,6 +1342,13 @@ public class VibratorService extends IVibratorService.Stub } else { pw.println("null"); } + pw.print(" mCurrentExternalVibration="); + if (mCurrentExternalVibration != null) { + pw.println(mCurrentExternalVibration.toString()); + } else { + pw.println("null"); + } + pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl); pw.println(" mLowPowerMode=" + mLowPowerMode); pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity); pw.println(" mNotificationIntensity=" + mNotificationIntensity); @@ -1310,6 +1369,87 @@ public class VibratorService extends IVibratorService.Stub new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver); } + final class ExternalVibratorService extends IExternalVibratorService.Stub { + @Override + public int onExternalVibrationStart(ExternalVibration vib) { + if (!mSupportsExternalControl) { + return SCALE_MUTE; + } + if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, + vib.getUid(), -1 /*owningUid*/, true /*exported*/) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + + " tried to play externally controlled vibration" + + " without VIBRATE permission, ignoring."); + return SCALE_MUTE; + } + + final int scaleLevel; + synchronized (mLock) { + if (!vib.equals(mCurrentExternalVibration)) { + if (mCurrentExternalVibration == null) { + // If we're not under external control right now, then cancel any normal + // vibration that may be playing and ready the vibrator for external + // control. + doCancelVibrateLocked(); + setVibratorUnderExternalControl(true); + } + // At this point we either have an externally controlled vibration playing, or + // no vibration playing. Since the interface defines that only one externally + // controlled vibration can play at a time, by returning something other than + // SCALE_MUTE from this function we can be assured that if we are currently + // playing vibration, it will be muted in favor of the new vibration. + // + // Note that this doesn't support multiple concurrent external controls, as we + // would need to mute the old one still if it came from a different controller. + mCurrentExternalVibration = vib; + if (DEBUG) { + Slog.e(TAG, "Playing external vibration: " + vib); + } + } + final int usage = vib.getAudioAttributes().getUsage(); + final int defaultIntensity; + final int currentIntensity; + if (isRingtone(usage)) { + defaultIntensity = mVibrator.getDefaultRingVibrationIntensity(); + currentIntensity = mRingIntensity; + } else if (isNotification(usage)) { + defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity(); + currentIntensity = mNotificationIntensity; + } else if (isHapticFeedback(usage)) { + defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity(); + currentIntensity = mHapticFeedbackIntensity; + } else { + defaultIntensity = 0; + currentIntensity = 0; + } + scaleLevel = currentIntensity - defaultIntensity; + } + if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { + return scaleLevel; + } else { + // Presumably we want to play this but something about our scaling has gone + // wrong, so just play with no scaling. + Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level " + + scaleLevel + " for vibration " + vib); + return SCALE_NONE; + } + } + + @Override + public void onExternalVibrationStop(ExternalVibration vib) { + synchronized (mLock) { + if (vib.equals(mCurrentExternalVibration)) { + mCurrentExternalVibration = null; + setVibratorUnderExternalControl(false); + if (DEBUG) { + Slog.e(TAG, "Stopping external vibration" + vib); + } + } + } + } + } + private final class VibratorShellCommand extends ShellCommand { private final IBinder mToken; diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 0aaea2f62de0..e42666c6a637 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -48,6 +48,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST = DEBUG_ALL || false; static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; + static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eb643b670fcd..d9f3c02e4292 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -224,7 +224,6 @@ import android.hardware.display.DisplayManagerInternal; import android.location.LocationManager; import android.media.audiofx.AudioEffect; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AppZygote; import android.os.BatteryStats; @@ -2090,6 +2089,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mBatteryStatsService.systemServicesReady(); mService.mServices.systemServicesReady(); + } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { + mService.startBroadcastObservers(); } } @@ -2266,12 +2267,27 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + // Broadcast policy parameters + final BroadcastConstants foreConstants = new BroadcastConstants( + Settings.Global.BROADCAST_FG_CONSTANTS); + foreConstants.TIMEOUT = BROADCAST_FG_TIMEOUT; + + final BroadcastConstants backConstants = new BroadcastConstants( + Settings.Global.BROADCAST_BG_CONSTANTS); + backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT; + + final BroadcastConstants offloadConstants = new BroadcastConstants( + Settings.Global.BROADCAST_OFFLOAD_CONSTANTS); + offloadConstants.TIMEOUT = BROADCAST_BG_TIMEOUT; + // by default, no "slow" policy in this queue + offloadConstants.SLOW_TIME = Integer.MAX_VALUE; + mFgBroadcastQueue = new BroadcastQueue(this, mHandler, - "foreground", BROADCAST_FG_TIMEOUT, false); + "foreground", foreConstants, false); mBgBroadcastQueue = new BroadcastQueue(this, mHandler, - "background", BROADCAST_BG_TIMEOUT, true); + "background", backConstants, true); mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler, - "offload", BROADCAST_BG_TIMEOUT, true); + "offload", offloadConstants, true); mBroadcastQueues[0] = mFgBroadcastQueue; mBroadcastQueues[1] = mBgBroadcastQueue; mBroadcastQueues[2] = mOffloadBroadcastQueue; @@ -5226,6 +5242,15 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean isIntentSenderABroadcast(IIntentSender pendingResult) { + if (pendingResult instanceof PendingIntentRecord) { + final PendingIntentRecord res = (PendingIntentRecord) pendingResult; + return res.key.type == ActivityManager.INTENT_SENDER_BROADCAST; + } + return false; + } + + @Override public Intent getIntentForIntentSender(IIntentSender pendingResult) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "getIntentForIntentSender()"); @@ -7211,7 +7236,6 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { mSystemProvidersInstalled = true; } - mConstants.start(mContext.getContentResolver()); mCoreSettingsObserver = new CoreSettingsObserver(this); mActivityTaskManager.installSystemProviders(); @@ -8711,6 +8735,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void startBroadcastObservers() { + for (BroadcastQueue queue : mBroadcastQueues) { + queue.start(mContext.getContentResolver()); + } + } + private void updateForceBackgroundCheck(boolean enabled) { synchronized (this) { if (mForceBackgroundCheck != enabled) { @@ -14909,10 +14939,7 @@ public class ActivityManagerService extends IActivityManager.Stub resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts); - if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r - + ": prev had " + queue.mOrderedBroadcasts.size()); - if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, - "Enqueueing broadcast " + r.intent.getAction()); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r); final BroadcastRecord oldRecord = replacePending ? queue.replaceOrderedBroadcastLocked(r) : null; @@ -15788,13 +15815,12 @@ public class ActivityManagerService extends IActivityManager.Stub * Returns true if things are idle enough to perform GCs. */ private final boolean canGcNowLocked() { - boolean processingBroadcasts = false; for (BroadcastQueue q : mBroadcastQueues) { - if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) { - processingBroadcasts = true; + if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) { + return false; } } - return !processingBroadcasts && mAtmInternal.canGcNow(); + return mAtmInternal.canGcNow(); } /** diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index a376e7a15410..08900328a200 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.app.ActivityManager; import android.app.job.JobProtoEnums; import android.bluetooth.BluetoothActivityEnergyInfo; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -46,6 +47,7 @@ import android.os.connectivity.WifiBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; import android.os.health.UidHealthStats; +import android.provider.Settings; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; @@ -1651,4 +1653,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub return new HealthStatsParceler(uidWriter); } + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * + * @hide + */ + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null); + final long ident = Binder.clearCallingIdentity(); + + try { + final ContentResolver contentResolver = mContext.getContentResolver(); + return Settings.Global.putLong(contentResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + delayMillis); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java new file mode 100644 index 000000000000..820caf12ac84 --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -0,0 +1,151 @@ +/* + * 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.am; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.KeyValueListParser; +import android.util.Slog; +import android.util.TimeUtils; + +import java.io.PrintWriter; + +/** + * Tunable parameters for broadcast dispatch policy + */ +public class BroadcastConstants { + private static final String TAG = "BroadcastConstants"; + + // Value element names within the Settings record + static final String KEY_TIMEOUT = "bcast_timeout"; + static final String KEY_SLOW_TIME = "bcast_slow_time"; + static final String KEY_DEFERRAL = "bcast_deferral"; + static final String KEY_DEFERRAL_DECAY_FACTOR = "bcast_deferral_decay_factor"; + static final String KEY_DEFERRAL_FLOOR = "bcast_deferral_floor"; + + // All time intervals are in milliseconds + private static final long DEFAULT_TIMEOUT = 10_000; + private static final long DEFAULT_SLOW_TIME = 5_000; + private static final long DEFAULT_DEFERRAL = 5_000; + private static final float DEFAULT_DEFERRAL_DECAY_FACTOR = 0.75f; + private static final long DEFAULT_DEFERRAL_FLOOR = 0; + + // All time constants are in milliseconds + + // Timeout period for this broadcast queue + public long TIMEOUT = DEFAULT_TIMEOUT; + // Handling time above which we declare that a broadcast recipient was "slow". Any + // value <= zero is interpreted as disabling broadcast deferral policy entirely. + public long SLOW_TIME = DEFAULT_SLOW_TIME; + // How long to initially defer broadcasts, if an app is slow to handle one + public long DEFERRAL = DEFAULT_DEFERRAL; + // Decay factor for successive broadcasts' deferral time + public float DEFERRAL_DECAY_FACTOR = DEFAULT_DEFERRAL_DECAY_FACTOR; + // Minimum that the deferral time can decay to until the backlog fully clears + public long DEFERRAL_FLOOR = DEFAULT_DEFERRAL_FLOOR; + + // Settings override tracking for this instance + private String mSettingsKey; + private SettingsObserver mSettingsObserver; + private ContentResolver mResolver; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + updateConstants(); + } + } + + // A given constants instance is configured to observe specific keys from which + // that instance's values are drawn. + public BroadcastConstants(String settingsKey) { + mSettingsKey = settingsKey; + } + + /** + * Spin up the observer lazily, since it can only happen once the settings provider + * has been brought into service + */ + public void startObserving(Handler handler, ContentResolver resolver) { + mResolver = resolver; + + mSettingsObserver = new SettingsObserver(handler); + mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey), + false, mSettingsObserver); + + updateConstants(); + } + + private void updateConstants() { + synchronized (mParser) { + try { + mParser.setString(Settings.Global.getString(mResolver, mSettingsKey)); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Bad broadcast settings in key '" + mSettingsKey + "'", e); + return; + } + + // Unspecified fields retain their current value rather than revert to default + TIMEOUT = mParser.getLong(KEY_TIMEOUT, TIMEOUT); + SLOW_TIME = mParser.getLong(KEY_SLOW_TIME, SLOW_TIME); + DEFERRAL = mParser.getLong(KEY_DEFERRAL, DEFERRAL); + DEFERRAL_DECAY_FACTOR = mParser.getFloat(KEY_DEFERRAL_DECAY_FACTOR, + DEFERRAL_DECAY_FACTOR); + DEFERRAL_FLOOR = mParser.getLong(KEY_DEFERRAL_FLOOR, DEFERRAL_FLOOR); + } + } + + /** + * Standard dumpsys support; invoked from BroadcastQueue dump + */ + public void dump(PrintWriter pw) { + synchronized (mParser) { + pw.println(); + pw.print(" Broadcast parameters (key="); + pw.print(mSettingsKey); + pw.print(", observing="); + pw.print(mSettingsObserver != null); + pw.println("):"); + + pw.print(" "); pw.print(KEY_TIMEOUT); pw.print(" = "); + TimeUtils.formatDuration(TIMEOUT, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_SLOW_TIME); pw.print(" = "); + TimeUtils.formatDuration(SLOW_TIME, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_DEFERRAL); pw.print(" = "); + TimeUtils.formatDuration(DEFERRAL, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_DEFERRAL_DECAY_FACTOR); pw.print(" = "); + pw.println(DEFERRAL_DECAY_FACTOR); + + pw.print(" "); pw.print(KEY_DEFERRAL_FLOOR); pw.print(" = "); + TimeUtils.formatDuration(DEFERRAL_FLOOR, pw); + pw.println(); + } + } +} diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java new file mode 100644 index 000000000000..0d46379112aa --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -0,0 +1,687 @@ +/* + * 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.am; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL; + +import android.content.Intent; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseIntArray; +import android.util.proto.ProtoOutputStream; + +import com.android.server.AlarmManagerInternal; +import com.android.server.LocalServices; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Set; + +/** + * Manages ordered broadcast delivery, applying policy to mitigate the effects of + * slow receivers. + */ +public class BroadcastDispatcher { + private static final String TAG = "BroadcastDispatcher"; + + // Deferred broadcasts to one app; times are all uptime time base like + // other broadcast-related timekeeping + static class Deferrals { + final int uid; + long deferredAt; // when we started deferring + long deferredBy; // how long did we defer by last time? + long deferUntil; // when does the next element become deliverable? + int alarmCount; + + final ArrayList<BroadcastRecord> broadcasts; + + Deferrals(int uid, long now, long backoff, int count) { + this.uid = uid; + this.deferredAt = now; + this.deferredBy = backoff; + this.deferUntil = now + backoff; + this.alarmCount = count; + broadcasts = new ArrayList<>(); + } + + void add(BroadcastRecord br) { + broadcasts.add(br); + } + + void writeToProto(ProtoOutputStream proto, long fieldId) { + for (BroadcastRecord br : broadcasts) { + br.writeToProto(proto, fieldId); + } + } + + void dumpLocked(Dumper d) { + for (BroadcastRecord br : broadcasts) { + d.dump(br); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Deferrals{uid="); + sb.append(uid); + sb.append(", deferUntil="); + sb.append(deferUntil); + sb.append(", #broadcasts="); + sb.append(broadcasts.size()); + sb.append("}"); + return sb.toString(); + } + } + + // Carrying dump formatting state across multiple concatenated datasets + class Dumper { + final PrintWriter mPw; + final String mQueueName; + final String mDumpPackage; + final SimpleDateFormat mSdf; + boolean mPrinted; + boolean mNeedSep; + String mHeading; + String mLabel; + int mOrdinal; + + Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) { + mPw = pw; + mQueueName = queueName; + mDumpPackage = dumpPackage; + mSdf = sdf; + + mPrinted = false; + mNeedSep = true; + } + + void setHeading(String heading) { + mHeading = heading; + mPrinted = false; + } + + void setLabel(String label) { + //" Active Ordered Broadcast " + mQueueName + " #" + i + ":" + mLabel = " " + label + " " + mQueueName + " #"; + mOrdinal = 0; + } + + boolean didPrint() { + return mPrinted; + } + + void dump(BroadcastRecord br) { + if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) { + if (!mPrinted) { + if (mNeedSep) { + mPw.println(); + } + mPrinted = true; + mNeedSep = true; + mPw.println(" " + mHeading + " [" + mQueueName + "]:"); + } + mPw.println(mLabel + mOrdinal + ":"); + mOrdinal++; + + br.dump(mPw, " ", mSdf); + } + } + } + + private final Object mLock; + private final BroadcastQueue mQueue; + private final BroadcastConstants mConstants; + private final Handler mHandler; + private AlarmManagerInternal mAlarm; + + // Current alarm targets; mapping uid -> in-flight alarm count + final SparseIntArray mAlarmUids = new SparseIntArray(); + final AlarmManagerInternal.InFlightListener mAlarmListener = + new AlarmManagerInternal.InFlightListener() { + @Override + public void broadcastAlarmPending(final int recipientUid) { + synchronized (mLock) { + final int newCount = mAlarmUids.get(recipientUid, 0) + 1; + mAlarmUids.put(recipientUid, newCount); + // any deferred broadcasts to this app now get fast-tracked + final int numEntries = mDeferredBroadcasts.size(); + for (int i = 0; i < numEntries; i++) { + if (recipientUid == mDeferredBroadcasts.get(i).uid) { + Deferrals d = mDeferredBroadcasts.remove(i); + mAlarmBroadcasts.add(d); + break; + } + } + } + } + + @Override + public void broadcastAlarmComplete(final int recipientUid) { + synchronized (mLock) { + final int newCount = mAlarmUids.get(recipientUid, 0) - 1; + if (newCount >= 0) { + mAlarmUids.put(recipientUid, newCount); + } else { + Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid); + mAlarmUids.put(recipientUid, 0); + } + + // No longer an alarm target, so resume ordinary deferral policy + if (newCount <= 0) { + final int numEntries = mAlarmBroadcasts.size(); + for (int i = 0; i < numEntries; i++) { + if (recipientUid == mAlarmBroadcasts.get(i).uid) { + Deferrals d = mAlarmBroadcasts.remove(i); + insertLocked(mDeferredBroadcasts, d); + break; + } + } + } + } + } + }; + + // Queue recheck operation used to tickle broadcast delivery when appropriate + final Runnable mScheduleRunnable = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.v(TAG, "Deferral recheck of pending broadcasts"); + } + mQueue.scheduleBroadcastsLocked(); + mRecheckScheduled = false; + } + } + }; + private boolean mRecheckScheduled = false; + + // Usual issuance-order outbound queue + private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>(); + // General deferrals not holding up alarms + private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>(); + // Deferrals that *are* holding up alarms; ordered by alarm dispatch time + private final ArrayList<Deferrals> mAlarmBroadcasts = new ArrayList<>(); + + // Next outbound broadcast, established by getNextBroadcastLocked() + private BroadcastRecord mCurrentBroadcast; + + /** + * Constructed & sharing a lock with its associated BroadcastQueue instance + */ + public BroadcastDispatcher(BroadcastQueue queue, BroadcastConstants constants, + Handler handler, Object lock) { + mQueue = queue; + mConstants = constants; + mHandler = handler; + mLock = lock; + } + + /** + * Spin up the integration with the alarm manager service; done lazily to manage + * service availability ordering during boot. + */ + public void start() { + // Set up broadcast alarm tracking + mAlarm = LocalServices.getService(AlarmManagerInternal.class); + mAlarm.registerInFlightListener(mAlarmListener); + } + + /** + * Standard contents-are-empty check + */ + public boolean isEmpty() { + synchronized (mLock) { + return mCurrentBroadcast == null + && mOrderedBroadcasts.isEmpty() + && mDeferredBroadcasts.isEmpty() + && mAlarmBroadcasts.isEmpty(); + } + } + + /** + * Not quite the traditional size() measurement; includes any in-process but + * not yet retired active outbound broadcast. + */ + public int totalUndelivered() { + synchronized (mLock) { + return mAlarmBroadcasts.size() + + mDeferredBroadcasts.size() + + mOrderedBroadcasts.size() + + (mCurrentBroadcast == null ? 0 : 1); + } + } + + // ---------------------------------- + // BroadcastQueue operation support + + void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + mOrderedBroadcasts.add(r); + } + + // Returns the now-replaced broadcast record, or null if none + BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) { + // Simple case, in the ordinary queue. + BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging); + + // If we didn't find it, less-simple: in a deferral queue? + if (old == null) { + old = replaceDeferredBroadcastLocked(mAlarmBroadcasts, r, typeForLogging); + } + if (old == null) { + old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging); + } + return old; + } + + private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list, + BroadcastRecord r, String typeForLogging) { + BroadcastRecord old; + final int numEntries = list.size(); + for (int i = 0; i < numEntries; i++) { + final Deferrals d = list.get(i); + old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging); + if (old != null) { + return old; + } + } + return null; + } + + private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list, + BroadcastRecord r, String typeForLogging) { + BroadcastRecord old; + final Intent intent = r.intent; + // Any in-flight broadcast has already been popped, and cannot be replaced. + // (This preserves existing behavior of the replacement API) + for (int i = list.size() - 1; i >= 0; i++) { + old = list.get(i); + if (old.userId == r.userId && intent.filterEquals(old.intent)) { + if (DEBUG_BROADCAST) { + Slog.v(TAG, "***** Replacing " + typeForLogging + + " [" + mQueue.mQueueName + "]: " + intent); + } + // Clone deferral state too if any + r.deferred = old.deferred; + list.set(i, r); + return old; + } + } + return null; + } + + boolean cleanupDisabledPackageReceiversLocked(final String packageName, + Set<String> filterByClasses, final int userId, final boolean doit) { + // Note: fast short circuits when 'doit' is false, as soon as we hit any + // "yes we would do something" circumstance + boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts, + packageName, filterByClasses, userId, doit); + if (doit || !didSomething) { + didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmBroadcasts, + packageName, filterByClasses, userId, doit); + } + if (doit || !didSomething) { + didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts, + packageName, filterByClasses, userId, doit); + } + if ((doit || !didSomething) && mCurrentBroadcast != null) { + didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked( + packageName, filterByClasses, userId, doit); + } + + return didSomething; + } + + private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list, + final String packageName, Set<String> filterByClasses, final int userId, + final boolean doit) { + boolean didSomething = false; + for (Deferrals d : list) { + didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts, + packageName, filterByClasses, userId, doit); + if (!doit && didSomething) { + return true; + } + } + return didSomething; + } + + private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list, + final String packageName, Set<String> filterByClasses, final int userId, + final boolean doit) { + boolean didSomething = false; + for (BroadcastRecord br : list) { + didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName, + filterByClasses, userId, doit); + if (!doit && didSomething) { + return true; + } + } + return didSomething; + } + + /** + * Standard proto dump entry point + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + if (mCurrentBroadcast != null) { + mCurrentBroadcast.writeToProto(proto, fieldId); + } + for (Deferrals d : mAlarmBroadcasts) { + d.writeToProto(proto, fieldId); + } + for (BroadcastRecord br : mOrderedBroadcasts) { + br.writeToProto(proto, fieldId); + } + for (Deferrals d : mDeferredBroadcasts) { + d.writeToProto(proto, fieldId); + } + } + + // ---------------------------------- + // Dispatch & deferral management + + public BroadcastRecord getActiveBroadcastLocked() { + return mCurrentBroadcast; + } + + /** + * If there is a deferred broadcast that is being sent to an alarm target, return + * that one. If there's no deferred alarm target broadcast but there is one + * that has reached the end of its deferral, return that. + * + * This stages the broadcast internally until it is retired, and returns that + * staged record if this is called repeatedly, until retireBroadcast(r) is called. + */ + public BroadcastRecord getNextBroadcastLocked(final long now) { + if (mCurrentBroadcast != null) { + return mCurrentBroadcast; + } + + BroadcastRecord next = null; + if (!mAlarmBroadcasts.isEmpty()) { + next = popLocked(mAlarmBroadcasts); + if (DEBUG_BROADCAST_DEFERRAL && next != null) { + Slog.i(TAG, "Next broadcast from alarm targets: " + next); + } + } + + if (next == null && !mDeferredBroadcasts.isEmpty()) { + for (int i = 0; i < mDeferredBroadcasts.size(); i++) { + Deferrals d = mDeferredBroadcasts.get(i); + if (now < d.deferUntil) { + // No more deferrals due + break; + } + + if (d.broadcasts.size() > 0) { + next = d.broadcasts.remove(0); + // apply deferral-interval decay policy and move this uid's + // deferred broadcasts down in the delivery queue accordingly + mDeferredBroadcasts.remove(i); // already 'd' + d.deferredBy = calculateDeferral(d.deferredBy); + d.deferUntil += d.deferredBy; + insertLocked(mDeferredBroadcasts, d); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Next broadcast from deferrals " + next + + ", deferUntil now " + d.deferUntil); + } + break; + } + } + } + + if (next == null && !mOrderedBroadcasts.isEmpty()) { + next = mOrderedBroadcasts.remove(0); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Next broadcast from main queue: " + next); + } + } + + mCurrentBroadcast = next; + return next; + } + + /** + * Called after the broadcast queue finishes processing the currently + * active broadcast (obtained by calling getNextBroadcastLocked()). + */ + public void retireBroadcastLocked(final BroadcastRecord r) { + // ERROR if 'r' is not the active broadcast + if (r != mCurrentBroadcast) { + Slog.wtf(TAG, "Retiring broadcast " + r + + " doesn't match current outgoing " + mCurrentBroadcast); + } + mCurrentBroadcast = null; + } + + /** + * Called prior to broadcast dispatch to check whether the intended + * recipient is currently subject to deferral policy. + */ + public boolean isDeferringLocked(final int uid) { + Deferrals d = findUidLocked(uid); + if (d != null && d.broadcasts.isEmpty()) { + // once we've caught up with deferred broadcasts to this uid + // and time has advanced sufficiently that we wouldn't be + // deferring newly-enqueued ones, we're back to normal policy. + if (SystemClock.uptimeMillis() >= d.deferUntil) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid); + } + removeDeferral(d); + return false; + } + } + return (d != null); + } + + /** + * Defer broadcasts for the given app. If 'br' is non-null, this also makes + * sure that broadcast record is enqueued as the next upcoming broadcast for + * the app. + */ + public void startDeferring(final int uid) { + synchronized (mLock) { + Deferrals d = findUidLocked(uid); + + // If we're not yet tracking this app, set up that bookkeeping + if (d == null) { + // Start a new deferral + final long now = SystemClock.uptimeMillis(); + d = new Deferrals(uid, + now, + mConstants.DEFERRAL, + mAlarmUids.get(uid, 0)); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Now deferring broadcasts to " + uid + + " until " + d.deferUntil); + } + // where it goes depends on whether it is coming into an alarm-related situation + if (d.alarmCount == 0) { + // common case, put it in the ordinary priority queue + insertLocked(mDeferredBroadcasts, d); + scheduleDeferralCheckLocked(true); + } else { + // alarm-related: strict order-encountered + mAlarmBroadcasts.add(d); + } + } else { + // We're already deferring, but something was slow again. Reset the + // deferral decay progression. + d.deferredBy = mConstants.DEFERRAL; + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to " + + d.deferredBy); + } + } + } + } + + /** + * Key entry point when a broadcast about to be delivered is instead + * set aside for deferred delivery + */ + public void addDeferredBroadcast(final int uid, BroadcastRecord br) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Enqueuing deferred broadcast " + br); + } + synchronized (mLock) { + Deferrals d = findUidLocked(uid); + if (d == null) { + Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid); + } else { + if (br == null) { + Slog.wtf(TAG, "Deferring null broadcast to " + uid); + } else { + br.deferred = true; + d.add(br); + } + } + } + } + + /** + * When there are deferred broadcasts, we need to make sure to recheck the + * dispatch queue when they come due. Alarm-sensitive deferrals get dispatched + * aggressively, so we only need to use the ordinary deferrals timing to figure + * out when to recheck. + */ + public void scheduleDeferralCheckLocked(boolean force) { + if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) { + final Deferrals d = mDeferredBroadcasts.get(0); + if (!d.broadcasts.isEmpty()) { + mHandler.removeCallbacks(mScheduleRunnable); + mHandler.postAtTime(mScheduleRunnable, d.deferUntil); + mRecheckScheduled = true; + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil); + } + } + } + } + + // ---------------------------------- + + /** + * If broadcasts to this uid are being deferred, find the deferrals record about it. + * @return null if this uid's broadcasts are not being deferred + */ + private Deferrals findUidLocked(final int uid) { + // The common case is that they it isn't also an alarm target... + Deferrals d = findUidLocked(uid, mDeferredBroadcasts); + // ...but if not there, also check alarm-prioritized deferrals + if (d == null) { + d = findUidLocked(uid, mAlarmBroadcasts); + } + return d; + } + + /** + * Remove the given deferral record from whichever queue it might be in at present + * @return true if the deferral was in fact found, false if this made no changes + */ + private boolean removeDeferral(Deferrals d) { + boolean didRemove = mDeferredBroadcasts.remove(d); + if (!didRemove) { + didRemove = mAlarmBroadcasts.remove(d); + } + return didRemove; + } + + /** + * Find the deferrals record for the given uid in the given list + */ + private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) { + final int numElements = list.size(); + for (int i = 0; i < numElements; i++) { + Deferrals d = list.get(i); + if (uid == d.uid) { + return d; + } + } + return null; + } + + /** + * Pop the next broadcast record from the head of the given deferrals list, + * if one exists. + */ + private static BroadcastRecord popLocked(ArrayList<Deferrals> list) { + final Deferrals d = list.get(0); + return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0); + } + + /** + * Insert the given Deferrals into the priority queue, sorted by defer-until milestone + */ + private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) { + // Simple linear search is appropriate here because we expect to + // have very few entries in the deferral lists (i.e. very few badly- + // behaving apps currently facing deferral) + int i; + final int numElements = list.size(); + for (i = 0; i < numElements; i++) { + if (d.deferUntil < list.get(i).deferUntil) { + break; + } + } + list.add(i, d); + } + + /** + * Calculate a new deferral time based on the previous time. This should decay + * toward zero, though a small nonzero floor is an option. + */ + private long calculateDeferral(long previous) { + return Math.max(mConstants.DEFERRAL_FLOOR, + (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR)); + } + + // ---------------------------------- + + boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName, + SimpleDateFormat sdf) { + final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf); + boolean printed = false; + + dumper.setHeading("Active ordered broadcasts"); + dumper.setLabel("Active Ordered Broadcast"); + for (Deferrals d : mAlarmBroadcasts) { + d.dumpLocked(dumper); + } + printed |= dumper.didPrint(); + + for (BroadcastRecord br : mOrderedBroadcasts) { + dumper.dump(br); + } + printed |= dumper.didPrint(); + + dumper.setHeading("Deferred ordered broadcasts"); + dumper.setLabel("Deferred Ordered Broadcast"); + for (Deferrals d : mDeferredBroadcasts) { + d.dumpLocked(dumper); + } + printed |= dumper.didPrint(); + + return printed; + } +} diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index cdf6e0ede865..64a36ef66f12 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -24,6 +24,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; @@ -45,6 +46,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; +import android.util.SparseIntArray; import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -75,14 +77,15 @@ public final class BroadcastQueue { final ActivityManagerService mService; /** - * Recognizable moniker for this queue + * Behavioral parameters such as timeouts and deferral policy, tracking Settings + * for runtime configurability */ - final String mQueueName; + final BroadcastConstants mConstants; /** - * Timeout period for this queue's broadcasts + * Recognizable moniker for this queue */ - final long mTimeoutPeriod; + final String mQueueName; /** * If true, we can delay broadcasts while waiting services to finish in the previous @@ -100,13 +103,18 @@ public final class BroadcastQueue { final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>(); /** - * List of all active broadcasts that are to be executed one at a time. - * The object at the top of the list is the currently activity broadcasts; - * those after it are waiting for the top to finish. As with parallel - * broadcasts, separate background- and foreground-priority queues are - * maintained. + * Tracking of the ordered broadcast queue, including deferral policy and alarm + * prioritization. */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>(); + final BroadcastDispatcher mDispatcher; + + /** + * Refcounting for completion callbacks of split/deferred broadcasts. The key + * is an opaque integer token assigned lazily when a broadcast is first split + * into multiple BroadcastRecord objects. + */ + final SparseIntArray mSplitRefcounts = new SparseIntArray(); + private int mNextToken = 0; /** * Historical data of past broadcasts, for debugging. This is a ring buffer @@ -173,7 +181,8 @@ public final class BroadcastQueue { switch (msg.what) { case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( - TAG_BROADCAST, "Received BROADCAST_INTENT_MSG"); + TAG_BROADCAST, "Received BROADCAST_INTENT_MSG [" + + mQueueName + "]"); processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { @@ -201,12 +210,19 @@ public final class BroadcastQueue { } BroadcastQueue(ActivityManagerService service, Handler handler, - String name, long timeoutPeriod, boolean allowDelayBehindServices) { + String name, BroadcastConstants constants, boolean allowDelayBehindServices) { mService = service; mHandler = new BroadcastHandler(handler.getLooper()); mQueueName = name; - mTimeoutPeriod = timeoutPeriod; mDelayBehindServices = allowDelayBehindServices; + + mConstants = constants; + mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService); + } + + void start(ContentResolver resolver) { + mDispatcher.start(); + mConstants.startObserving(mHandler, resolver); } @Override @@ -224,7 +240,7 @@ public final class BroadcastQueue { } public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { - mOrderedBroadcasts.add(r); + mDispatcher.enqueueOrderedBroadcastLocked(r); enqueueBroadcastHelper(r); } @@ -255,7 +271,7 @@ public final class BroadcastQueue { * the old one. */ public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) { - return replaceBroadcastLocked(mOrderedBroadcasts, r, "ORDERED"); + return mDispatcher.replaceBroadcastLocked(r, "ORDERED"); } private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue, @@ -365,14 +381,17 @@ public final class BroadcastQueue { } } + // Skip the current receiver, if any, that is in flight to the given process public void skipCurrentReceiverLocked(ProcessRecord app) { BroadcastRecord r = null; - if (mOrderedBroadcasts.size() > 0) { - BroadcastRecord br = mOrderedBroadcasts.get(0); - if (br.curApp == app) { - r = br; - } + final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked(); + if (curActive != null && curActive.curApp == app) { + // confirmed: the current active broadcast is to the given app + r = curActive; } + + // If the current active broadcast isn't this BUT we're waiting for + // mPendingBroadcast to spin up the target app, that's what we use. if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "[" + mQueueName + "] skip & discard pending app " + r); @@ -404,20 +423,29 @@ public final class BroadcastQueue { } public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { - if (mOrderedBroadcasts.size() > 0) { - final BroadcastRecord r = mOrderedBroadcasts.get(0); - if (r != null && r.receiver == receiver) { - return r; - } + BroadcastRecord br = mDispatcher.getActiveBroadcastLocked(); + if (br != null && br.receiver == receiver) { + return br; } return null; } + // > 0 only, no worry about "eventual" recycling + private int nextSplitTokenLocked() { + int next = mNextToken + 1; + if (next <= 0) { + next = 1; + } + mNextToken = next; + return next; + } + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) { final int state = r.state; final ActivityInfo receiver = r.curReceiver; final long finishTime = SystemClock.uptimeMillis(); + final long elapsed = finishTime - r.receiverTime; r.state = BroadcastRecord.IDLE; if (state == BroadcastRecord.IDLE) { Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); @@ -428,8 +456,22 @@ public final class BroadcastQueue { // If we're abandoning this broadcast before any receivers were actually spun up, // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply. if (r.nextReceiver > 0) { - r.duration[r.nextReceiver - 1] = finishTime - r.receiverTime; + r.duration[r.nextReceiver - 1] = elapsed; + } + + // if this receiver was slow, impose deferral policy on the app. This will kick in + // when processNextBroadcastLocked() next finds this uid as a receiver identity. + if (mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Broadcast receiver was slow: " + receiver + " br=" + r); + } + if (r.curApp != null) { + mDispatcher.startDeferring(r.curApp.uid); + } else { + Slog.d(TAG, "finish receiver curApp is null? " + r); + } } + r.receiver = null; r.intent.setComponent(null); if (r.curApp != null && r.curApp.curReceivers.contains(r)) { @@ -452,9 +494,10 @@ public final class BroadcastQueue { r.resultAbort = false; } + // If we want to wait behind services *AND* we're finishing the head/ + // active broadcast on its queue if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices - && r.queue.mOrderedBroadcasts.size() > 0 - && r.queue.mOrderedBroadcasts.get(0) == r) { + && r.queue.mDispatcher.getActiveBroadcastLocked() == r) { ActivityInfo nextReceiver; if (r.nextReceiver < r.receivers.size()) { Object obj = r.receivers.get(r.nextReceiver); @@ -488,8 +531,8 @@ public final class BroadcastQueue { } public void backgroundServicesFinishedLocked(int userId) { - if (mOrderedBroadcasts.size() > 0) { - BroadcastRecord br = mOrderedBroadcasts.get(0); + BroadcastRecord br = mDispatcher.getActiveBroadcastLocked(); + if (br != null) { if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) { Slog.i(TAG, "Resuming delayed broadcast"); br.curComponent = null; @@ -861,7 +904,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast [" + mQueueName + "]: " + mParallelBroadcasts.size() + " parallel broadcasts, " - + mOrderedBroadcasts.size() + " ordered broadcasts"); + + mDispatcher.totalUndelivered() + " ordered broadcasts"); mService.updateCpuStats(); @@ -937,8 +980,12 @@ public final class BroadcastQueue { boolean looped = false; do { - if (mOrderedBroadcasts.size() == 0) { - // No more broadcasts pending, so all done! + final long now = SystemClock.uptimeMillis(); + r = mDispatcher.getNextBroadcastLocked(now); + + if (r == null) { + // No more broadcasts are deliverable right now, so all done! + mDispatcher.scheduleDeferralCheckLocked(false); mService.scheduleAppGcsLocked(); if (looped) { // If we had finished the last ordered broadcast, then @@ -954,7 +1001,7 @@ public final class BroadcastQueue { return; } - r = mOrderedBroadcasts.get(0); + boolean forceReceive = false; // Ensure that even if something goes awry with the timeout @@ -967,9 +1014,8 @@ public final class BroadcastQueue { // significant amounts of time. int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; if (mService.mProcessesReady && r.dispatchTime > 0) { - long now = SystemClock.uptimeMillis(); if ((numReceivers > 0) && - (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { + (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) { Slog.w(TAG, "Hung broadcast [" + mQueueName + "] discarded after timeout failure:" + " now=" + now @@ -993,27 +1039,53 @@ public final class BroadcastQueue { return; } + // Is the current broadcast is done for any reason? if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { - // No more receivers for this broadcast! Send the final - // result if requested... + // Send the final result if requested if (r.resultTo != null) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, - "Finishing broadcast [" + mQueueName + "] " - + r.intent.getAction() + " app=" + r.callerApp); - performReceiveLocked(r.callerApp, r.resultTo, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false, r.userId); - // Set this to null so that the reference - // (local and remote) isn't kept in the mBroadcastHistory. - r.resultTo = null; - } catch (RemoteException e) { - r.resultTo = null; - Slog.w(TAG, "Failure [" - + mQueueName + "] sending broadcast result of " - + r.intent, e); - + boolean sendResult = true; + + // if this was part of a split/deferral complex, update the refcount and only + // send the completion when we clear all of them + if (r.splitToken != 0) { + int newCount = mSplitRefcounts.get(r.splitToken) - 1; + if (newCount == 0) { + // done! clear out this record's bookkeeping and deliver + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Sending broadcast completion for split token " + + r.splitToken); + } + mSplitRefcounts.delete(r.splitToken); + } else { + // still have some split broadcast records in flight; update refcount + // and hold off on the callback + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Result refcount " + newCount + " for split token " + + r.splitToken + " - not sending completion yet"); + } + sendResult = false; + mSplitRefcounts.put(r.splitToken, newCount); + } + } + if (sendResult) { + try { + if (DEBUG_BROADCAST) { + Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] " + + r.intent.getAction() + " app=" + r.callerApp); + } + performReceiveLocked(r.callerApp, r.resultTo, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, false, false, r.userId); + // Set this to null so that the reference + // (local and remote) isn't kept in the mBroadcastHistory. + r.resultTo = null; + } catch (RemoteException e) { + r.resultTo = null; + Slog.w(TAG, "Failure [" + + mQueueName + "] sending broadcast result of " + + r.intent, e); + } } } @@ -1031,11 +1103,76 @@ public final class BroadcastQueue { mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime); } - mOrderedBroadcasts.remove(0); + mDispatcher.retireBroadcastLocked(r); r = null; looped = true; continue; } + + // Check whether the next receiver is under deferral policy, and handle that + // accordingly. If the current broadcast was already part of deferred-delivery + // tracking, we know that it must now be deliverable as-is without re-deferral. + if (!r.deferred) { + final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver)); + if (mDispatcher.isDeferringLocked(receiverUid)) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid + + " at " + r.nextReceiver + " is under deferral"); + } + // If this is the only (remaining) receiver in the broadcast, "splitting" + // doesn't make sense -- just defer it as-is and retire it as the + // currently active outgoing broadcast. + BroadcastRecord defer; + if (r.nextReceiver + 1 == numReceivers) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Sole receiver of " + r + + " is under deferral; setting aside and proceeding"); + } + defer = r; + mDispatcher.retireBroadcastLocked(r); + } else { + // Nontrivial case; split out 'uid's receivers to a new broadcast record + // and defer that, then loop and pick up continuing delivery of the current + // record (now absent those receivers). + + // The split operation is guaranteed to match at least at 'nextReceiver' + defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG_BROADCAST, "Post split:"); + Slog.i(TAG_BROADCAST, "Original broadcast receivers:"); + for (int i = 0; i < r.receivers.size(); i++) { + Slog.i(TAG_BROADCAST, " " + r.receivers.get(i)); + } + Slog.i(TAG_BROADCAST, "Split receivers:"); + for (int i = 0; i < defer.receivers.size(); i++) { + Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i)); + } + } + // Track completion refcount as well if relevant + if (r.resultTo != null) { + int token = r.splitToken; + if (token == 0) { + // first split of this record; refcount for 'r' and 'deferred' + r.splitToken = defer.splitToken = nextSplitTokenLocked(); + mSplitRefcounts.put(r.splitToken, 2); + } else { + // new split from an already-refcounted situation; increment count + final int curCount = mSplitRefcounts.get(token); + if (DEBUG_BROADCAST_DEFERRAL) { + if (curCount == 0) { + Slog.wtf(TAG, "Split refcount is zero with token for " + r); + } + } + mSplitRefcounts.put(token, curCount + 1); + } + } + } + mDispatcher.addDeferredBroadcast(receiverUid, defer); + r = null; + looped = true; + continue; + } + } } while (r == null); // Get the next receiver... @@ -1066,7 +1203,7 @@ public final class BroadcastQueue { + mQueueName + "] " + r); } if (! mPendingBroadcastTimeoutMessage) { - long timeoutTime = r.receiverTime + mTimeoutPeriod; + long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Submitting BROADCAST_TIMEOUT_MSG [" + mQueueName + "] for " + r + " at " + timeoutTime); @@ -1474,12 +1611,12 @@ public final class BroadcastQueue { mPendingBroadcastTimeoutMessage = false; } - if (mOrderedBroadcasts.size() == 0) { + if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) { return; } long now = SystemClock.uptimeMillis(); - BroadcastRecord r = mOrderedBroadcasts.get(0); + BroadcastRecord r = mDispatcher.getActiveBroadcastLocked(); if (fromMsg) { if (!mService.mProcessesReady) { // Only process broadcast timeouts if the system is ready. That way @@ -1488,7 +1625,7 @@ public final class BroadcastQueue { return; } - long timeoutTime = r.receiverTime + mTimeoutPeriod; + long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (timeoutTime > now) { // We can observe premature timeouts because we do not cancel and reset the // broadcast timeout message after each receiver finishes. Instead, we set up @@ -1503,16 +1640,15 @@ public final class BroadcastQueue { } } - BroadcastRecord br = mOrderedBroadcasts.get(0); - if (br.state == BroadcastRecord.WAITING_SERVICES) { + if (r.state == BroadcastRecord.WAITING_SERVICES) { // In this case the broadcast had already finished, but we had decided to wait // for started services to finish as well before going on. So if we have actually // waited long enough time timeout the broadcast, let's give up on the whole thing // and just move on to the next. - Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null - ? br.curComponent.flattenToShortString() : "(null)")); - br.curComponent = null; - br.state = BroadcastRecord.IDLE; + Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null + ? r.curComponent.flattenToShortString() : "(null)")); + r.curComponent = null; + r.state = BroadcastRecord.IDLE; processNextBroadcast(false); return; } @@ -1619,13 +1755,8 @@ public final class BroadcastQueue { } } - for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) { - didSomething |= mOrderedBroadcasts.get(i).cleanupDisabledPackageReceiversLocked( - packageName, filterByClasses, userId, doit); - if (!doit && didSomething) { - return true; - } - } + didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName, + filterByClasses, userId, doit); return didSomething; } @@ -1665,7 +1796,7 @@ public final class BroadcastQueue { } final boolean isIdle() { - return mParallelBroadcasts.isEmpty() && mOrderedBroadcasts.isEmpty() + return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty() && (mPendingBroadcast == null); } @@ -1677,10 +1808,7 @@ public final class BroadcastQueue { for (int i = N - 1; i >= 0; i--) { mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS); } - N = mOrderedBroadcasts.size(); - for (int i = N - 1; i >= 0; i--) { - mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS); - } + mDispatcher.writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS); if (mPendingBroadcast != null) { mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST); } @@ -1721,7 +1849,7 @@ public final class BroadcastQueue { final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, boolean needSep) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 + if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty() || mPendingBroadcast != null) { boolean printed = false; for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) { @@ -1740,29 +1868,12 @@ public final class BroadcastQueue { pw.println(" Active Broadcast " + mQueueName + " #" + i + ":"); br.dump(pw, " ", sdf); } - printed = false; - needSep = true; - for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) { - BroadcastRecord br = mOrderedBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - printed = true; - pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); - } - pw.println(" Active Ordered Broadcast " + mQueueName + " #" + i + ":"); - mOrderedBroadcasts.get(i).dump(pw, " ", sdf); - } + + mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf); + if (dumpPackage == null || (mPendingBroadcast != null && dumpPackage.equals(mPendingBroadcast.callerPackage))) { - if (needSep) { - pw.println(); - } + pw.println(); pw.println(" Pending broadcast [" + mQueueName + "]:"); if (mPendingBroadcast != null) { mPendingBroadcast.dump(pw, " ", sdf); @@ -1773,6 +1884,8 @@ public final class BroadcastQueue { } } + mConstants.dump(pw); + int i; boolean printed = false; diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 9e799f6f14f3..d9e03f8f908f 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -36,10 +36,12 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * An active intent broadcast. @@ -64,6 +66,9 @@ final class BroadcastRecord extends Binder { final int[] delivery; // delivery state of each receiver final long[] duration; // duration a receiver took to process broadcast IIntentReceiver resultTo; // who receives final result if non-null + boolean deferred; + int splitCount; // refcount for result callback, when split + int splitToken; // identifier for cross-BroadcastRecord refcount long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers long dispatchClockTime; // the clock time the dispatch started @@ -106,6 +111,9 @@ final class BroadcastRecord extends Binder { ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // info about the receiver that is currently running. + // Private refcount-management bookkeeping; start > 0 + static AtomicInteger sNextToken = new AtomicInteger(1); + void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) { final long now = SystemClock.uptimeMillis(); @@ -304,6 +312,52 @@ final class BroadcastRecord extends Binder { allowBackgroundActivityStarts = from.allowBackgroundActivityStarts; } + /** + * Split off a new BroadcastRecord that clones this one, but contains only the + * recipient records for the current (just-finished) receiver's app, starting + * after the just-finished receiver [i.e. at r.nextReceiver]. Returns null + * if there are no matching subsequent receivers in this BroadcastRecord. + */ + BroadcastRecord splitRecipientsLocked(int slowAppUid, int startingAt) { + // Do we actually have any matching receivers down the line...? + ArrayList splitReceivers = null; + for (int i = startingAt; i < receivers.size(); ) { + Object o = receivers.get(i); + if (getReceiverUid(o) == slowAppUid) { + if (splitReceivers == null) { + splitReceivers = new ArrayList<>(); + } + splitReceivers.add(o); + receivers.remove(i); + break; + } else { + i++; + } + } + + // No later receivers in the same app, so we have no more to do + if (splitReceivers == null) { + return null; + } + + // build a new BroadcastRecord around that single-target list + BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, + requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode, + resultData, resultExtras, ordered, sticky, initialSticky, userId, + allowBackgroundActivityStarts); + + return split; + } + + int getReceiverUid(Object receiver) { + if (receiver instanceof BroadcastFilter) { + return ((BroadcastFilter) receiver).owningUid; + } else /* if (receiver instanceof ResolveInfo) */ { + return ((ResolveInfo) receiver).activityInfo.applicationInfo.uid; + } + } + public BroadcastRecord maybeStripForHistory() { if (!intent.canStripForHistory()) { return this; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7ede6dcd41bd..26d2d17fec7d 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -16,8 +16,8 @@ package com.android.server.appop; -import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_NONE; +import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; @@ -94,9 +94,9 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; - import com.android.server.LocalServices; import com.android.server.LockGuard; + import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; @@ -1282,6 +1282,46 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * Set all {@link #setMode (package) modes} for this uid to the default value. + * + * @param code The app-op + * @param uid The uid + */ + private void setAllPkgModesToDefault(int code, int uid) { + synchronized (this) { + UidState uidState = getUidStateLocked(uid, false); + if (uidState == null) { + return; + } + + ArrayMap<String, Ops> pkgOps = uidState.pkgOps; + if (pkgOps == null) { + return; + } + + int numPkgs = pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = pkgOps.valueAt(pkgNum); + + Op op = ops.get(code); + if (op == null) { + continue; + } + + int defaultMode = AppOpsManager.opToDefaultMode(code); + if (op.mode != defaultMode) { + Slog.w(TAG, "resetting app-op mode for " + AppOpsManager.opToName(code) + " of " + + pkgOps.keyAt(pkgNum)); + + op.mode = defaultMode; + + scheduleWriteLocked(); + } + } + } + } + @Override public void setMode(int code, int uid, String packageName, int mode) { setMode(code, uid, packageName, mode, true, false); @@ -4387,5 +4427,10 @@ public class AppOpsService extends IAppOpsService.Stub { public void setUidMode(int code, int uid, int mode) { AppOpsService.this.setUidMode(code, uid, mode); } + + @Override + public void setAllPkgModesToDefault(int code, int uid) { + AppOpsService.this.setAllPkgModesToDefault(code, uid); + } } } diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 27edbbf4f2d5..5b469fe2dc7a 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -16,6 +16,10 @@ package com.android.server.attention; +import static android.provider.DeviceConfig.AttentionManagerService.NAMESPACE; +import static android.provider.DeviceConfig.AttentionManagerService.PROPERTY_COMPONENT_NAME; +import static android.provider.DeviceConfig.AttentionManagerService.PROPERTY_SERVICE_ENABLED; + import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -40,6 +44,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.service.attention.AttentionService; import android.service.attention.AttentionService.AttentionFailureCodes; import android.service.attention.IAttentionCallback; @@ -66,6 +71,9 @@ import java.io.PrintWriter; public class AttentionManagerService extends SystemService { private static final String LOG_TAG = "AttentionManagerService"; + /** Default value in absence of {@link DeviceConfig} override. */ + private static final boolean DEFAULT_SERVICE_ENABLED = true; + /** Service will unbind if connection is not used for that amount of time. */ private static final long CONNECTION_TTL_MILLIS = 60_000; @@ -105,7 +113,7 @@ public class AttentionManagerService extends SystemService { super.onBootPhase(phase); if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { mComponentName = resolveAttentionService(mContext); - if (mComponentName != null) { + if (isAttentionServiceSupported()) { // If the service is supported we want to keep receiving the screen off events. mContext.registerReceiver(new ScreenStateReceiver(), new IntentFilter(Intent.ACTION_SCREEN_OFF)); @@ -117,7 +125,12 @@ public class AttentionManagerService extends SystemService { * Returns {@code true} if attention service is supported on this device. */ public boolean isAttentionServiceSupported() { - return mComponentName != null; + return mComponentName != null && isServiceEnabled(); + } + + private boolean isServiceEnabled() { + final String enabled = DeviceConfig.getProperty(NAMESPACE, PROPERTY_SERVICE_ENABLED); + return enabled == null ? DEFAULT_SERVICE_ENABLED : "true".equals(enabled); } /** @@ -174,10 +187,11 @@ public class AttentionManagerService extends SystemService { @Override public void onSuccess(int requestCode, int result, long timestamp) { callback.onSuccess(requestCode, result, timestamp); - userState.mAttentionCheckCache = new AttentionCheckCache( - SystemClock.uptimeMillis(), result, - timestamp); - + synchronized (mLock) { + userState.mAttentionCheckCache = new AttentionCheckCache( + SystemClock.uptimeMillis(), result, + timestamp); + } StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, result); } @@ -265,8 +279,9 @@ public class AttentionManagerService extends SystemService { * system. */ private static ComponentName resolveAttentionService(Context context) { - // TODO(b/111939367): add a flag to turn on/off. - final String componentNameString = context.getString( + final String flag = DeviceConfig.getProperty(NAMESPACE, PROPERTY_COMPONENT_NAME); + + final String componentNameString = flag != null ? flag : context.getString( R.string.config_defaultAttentionService); if (TextUtils.isEmpty(componentNameString)) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java new file mode 100644 index 000000000000..d652f93ccf04 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -0,0 +1,807 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** @hide */ +/*package*/ final class AudioDeviceBroker { + + private static final String TAG = "AudioDeviceBroker"; + + private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s + + /*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000; + // Timeout for connection to bluetooth headset service + /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; + + private final @NonNull AudioService mAudioService; + private final @NonNull Context mContext; + + /** Forced device usage for communications sent to AudioSystem */ + private int mForcedUseForComm; + /** + * Externally reported force device usage state returned by getters: always consistent + * with requests by setters */ + private int mForcedUseForCommExt; + + // Manages all connected devices, only ever accessed on the message loop + //### or make it synchronized + private final AudioDeviceInventory mDeviceInventory; + // Manages notifications to BT service + private final BtHelper mBtHelper; + + + //------------------------------------------------------------------- + private static final Object sLastDeviceConnectionMsgTimeLock = new Object(); + private static long sLastDeviceConnectMsgTime = 0; + + private final Object mBluetoothA2dpEnabledLock = new Object(); + // Request to override default use of A2DP for media. + @GuardedBy("mBluetoothA2dpEnabledLock") + private boolean mBluetoothA2dpEnabled; + + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mA2dpAvrcpLock = new Object(); + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mHearingAidLock = new Object(); + + // lock always taken when accessing AudioService.mSetModeDeathHandlers + /*package*/ final Object mSetModeLock = new Object(); + + //------------------------------------------------------------------- + /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { + mContext = context; + mAudioService = service; + setupMessaging(context); + mBtHelper = new BtHelper(this); + mDeviceInventory = new AudioDeviceInventory(this); + + mForcedUseForComm = AudioSystem.FORCE_NONE; + mForcedUseForCommExt = mForcedUseForComm; + + } + + /*package*/ Context getContext() { + return mContext; + } + + //--------------------------------------------------------------------- + // Communication from AudioService + // All methods are asynchronous and never block + // All permission checks are done in AudioService, all incoming calls are considered "safe" + // All post* methods are asynchronous + + /*package*/ void onSystemReady() { + mBtHelper.onSystemReady(); + } + + /*package*/ void onAudioServerDied() { + // Restore forced usage for communications and record + onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied"); + onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied"); + // restore devices + sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); + } + + /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) { + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + useCase, config, eventSource); + } + + /*package*/ void toggleHdmiIfConnected_Async() { + sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE); + } + + /*package*/ void disconnectAllBluetoothProfiles() { + mBtHelper.disconnectAllBluetoothProfiles(); + } + + /** + * Handle BluetoothHeadset intents where the action is one of + * {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or + * {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}. + * @param intent + */ + /*package*/ void receiveBtEvent(@NonNull Intent intent) { + mBtHelper.receiveBtEvent(intent); + } + + /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { + synchronized (mBluetoothA2dpEnabledLock) { + if (mBluetoothA2dpEnabled == on) { + return; + } + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + source); + } + } + + /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) { + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + + mForcedUseForCommExt = mForcedUseForComm; + setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + } + + /*package*/ boolean isSpeakerphoneOn() { + return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + } + + /*package*/ void setWiredDeviceConnectionState(int type, + @AudioService.ConnectionState int state, String address, String name, + String caller) { + //TODO move logging here just like in setBluetooth* methods + mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + } + + /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state + // only querying address as this is the only readily available field + // on the device + + " addr=" + device.getAddress() + + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent + + " vol=" + a2dpVolume)).printLog(TAG)); + if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(device))) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP connection state ignored")); + return 0; + } + return mDeviceInventory.setBluetoothA2dpDeviceConnectionState( + device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setHearingAidDeviceConnectionState state=" + state + + " addr=" + device.getAddress() + + " supprNoisy=" + suppressNoisyIntent + + " src=" + eventSource)).printLog(TAG)); + return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice); + } + + // never called by system components + /*package*/ void setBluetoothScoOnByApp(boolean on) { + mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + } + + /*package*/ boolean isBluetoothScoOnForApp() { + return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; + } + + /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { + //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); + if (on) { + // do not accept SCO ON if SCO audio is not connected + if (!mBtHelper.isBluetoothScoOn()) { + mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; + return; + } + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + mForcedUseForCommExt = mForcedUseForComm; + AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); + // Un-mute ringtone stream volume + mAudioService.setUpdateRingerModeServiceInt(); + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + return mDeviceInventory.startWatchingRoutes(observer); + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mDeviceInventory.getCurAudioRoutes(); + } + + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.isAvrcpAbsoluteVolumeSupported(); + } + } + + /*package*/ boolean isBluetoothA2dpOn() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) { + sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index); + } + + /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) { + sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType); + } + + /*package*/ void postDisconnectBluetoothSco(int exceptPid) { + sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid); + } + + /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { + sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); + } + + /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); + } + + /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) { + mBtHelper.stopBluetoothScoForClient(cb, eventSource); + } + + //--------------------------------------------------------------------- + // Communication with (to) AudioService + //TODO check whether the AudioService methods are candidates to move here + /*package*/ void postAccessoryPlugMediaUnmute(int device) { + mAudioService.postAccessoryPlugMediaUnmute(device); + } + + /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) { + return mAudioService.getStreamState(streamType); + } + + /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() { + return mAudioService.mSetModeDeathHandlers; + } + + /*package*/ int getDeviceForStream(int streamType) { + return mAudioService.getDeviceForStream(streamType); + } + + /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) { + mAudioService.setDeviceVolume(streamState, device); + } + + /*packages*/ void observeDevicesForAllStreams() { + mAudioService.observeDevicesForAllStreams(); + } + + /*package*/ boolean isInCommunication() { + return mAudioService.isInCommunication(); + } + + /*package*/ boolean hasMediaDynamicPolicy() { + return mAudioService.hasMediaDynamicPolicy(); + } + + /*package*/ ContentResolver getContentResolver() { + return mAudioService.getContentResolver(); + } + + /*package*/ void checkMusicActive(int deviceType, String caller) { + mAudioService.checkMusicActive(deviceType, caller); + } + + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + mAudioService.checkVolumeCecOnHdmiConnection(state, caller); + } + + //--------------------------------------------------------------------- + // Message handling on behalf of helper classes + /*package*/ void broadcastScoConnectionState(int state) { + sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state); + } + + /*package*/ void broadcastBecomingNoisy() { + sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); + } + + //###TODO unify with handleSetA2dpSinkConnectionState + /*package*/ void postA2dpSinkConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + //###TODO unify with handleSetA2dpSourceConnectionState + /*package*/ void postA2dpSourceConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void postSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) { + sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay); + } + + /*package*/ void postSetHearingAidConnectionState( + @AudioService.BtProfileConnectionState int state, + @NonNull BluetoothDevice device, int delay) { + sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE, + state, + device, + delay); + } + + //--------------------------------------------------------------------- + // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory) + // only call from a "handle"* method or "on"* method + + // Handles request to override default use of A2DP for media. + //@GuardedBy("mConnectedDevices") + /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) { + // for logging only + final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).append(" src:").append(source).toString(); + + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + onSetForceUse( + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + eventSource); + } + } + + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + } + + /*package*/ void handleDisconnectA2dp() { + mDeviceInventory.disconnectA2dp(); + } + /*package*/ void handleDisconnectA2dpSink() { + mDeviceInventory.disconnectA2dpSink(); + } + + /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version? + // might be ok here because called on BT thread? + sync happening in + // checkSendBecomingNoisyIntent + final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, + AudioSystem.DEVICE_NONE); + final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo + + " state= " + state + + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock()); + } + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void handleDisconnectHearingAid() { + mDeviceInventory.disconnectHearingAid(); + } + + /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state, + btDeviceInfo); + } + + /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) { + sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay); + } + + /*package*/ void handleCancelFailureToConnectToBtHeadsetService() { + mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + } + + /*package*/ void postReportNewRoutes() { + sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP); + } + + /*package*/ void cancelA2dpDockTimeout() { + mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpDockTimeout() { + return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { + return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(btDevice)); + } + + /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { + sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); + } + + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + synchronized (mA2dpAvrcpLock) { + mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); + } + } + + /*package*/ boolean getBluetoothA2dpEnabled() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.getA2dpCodec(device); + } + } + + //--------------------------------------------------------------------- + // Internal handling of messages + // These methods are ALL synchronous, in response to message handling in BrokerHandler + // Blocking in any of those will block the message queue + + private void onSetForceUse(int useCase, int config, String eventSource) { + if (useCase == AudioSystem.FOR_MEDIA) { + postReportNewRoutes(); + } + AudioService.sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } + + private void onSendBecomingNoisyIntent() { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + } + + //--------------------------------------------------------------------- + // Message handling + private BrokerHandler mBrokerHandler; + private BrokerThread mBrokerThread; + private PowerManager.WakeLock mBrokerEventWakeLock; + + private void setupMessaging(Context ctxt) { + final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE); + mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "handleAudioDeviceEvent"); + mBrokerThread = new BrokerThread(); + mBrokerThread.start(); + waitForBrokerHandlerCreation(); + } + + private void waitForBrokerHandlerCreation() { + synchronized (this) { + while (mBrokerHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + Log.e(TAG, "Interruption while waiting on BrokerHandler"); + } + } + } + } + + /** Class that handles the device broker's message queue */ + private class BrokerThread extends Thread { + BrokerThread() { + super("AudioDeviceBroker"); + } + + @Override + public void run() { + // Set this thread up so the handler will work on it + Looper.prepare(); + + synchronized (AudioDeviceBroker.this) { + mBrokerHandler = new BrokerHandler(); + + // Notify that the handler has been created + AudioDeviceBroker.this.notify(); + } + + Looper.loop(); + } + } + + /** Class that handles the message queue */ + private class BrokerHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_RESTORE_DEVICES: + mDeviceInventory.onRestoreDevices(); + synchronized (mBluetoothA2dpEnabledLock) { + mBtHelper.onAudioServerDiedRestoreA2dp(); + } + break; + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + mDeviceInventory.onSetWiredDeviceConnectionState( + (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj); + break; + case MSG_I_BROADCAST_BT_CONNECTION_STATE: + mBtHelper.onBroadcastScoConnectionState(msg.arg1); + break; + case MSG_IIL_SET_FORCE_USE: // intented fall-through + case MSG_IIL_SET_FORCE_BT_A2DP_USE: + onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj); + break; + case MSG_REPORT_NEW_ROUTES: + mDeviceInventory.onReportNewRoutes(); + break; + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSinkConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2); + break; + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSourceConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + break; + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + mDeviceInventory.onSetHearingAidConnectionState( + (BluetoothDevice) msg.obj, msg.arg1); + break; + case MSG_BT_HEADSET_CNCT_FAILED: + mBtHelper.resetBluetoothSco(); + break; + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + // msg.obj == address of BTA2DP device + mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); + break; + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + final int a2dpCodec; + final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + synchronized (mA2dpAvrcpLock) { + a2dpCodec = mBtHelper.getA2dpCodec(btDevice); + } + mDeviceInventory.onBluetoothA2dpDeviceConfigChange( + new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec)); + break; + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; + case MSG_II_SET_HEARING_AID_VOLUME: + mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2); + break; + case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME: + mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); + break; + case MSG_I_DISCONNECT_BT_SCO: + mBtHelper.disconnectBluetoothSco(msg.arg1); + break; + case MSG_TOGGLE_HDMI: + mDeviceInventory.onToggleHdmi(); + break; + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + mDeviceInventory.onBluetoothA2dpActiveDeviceChange( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj); + break; + default: + Log.wtf(TAG, "Invalid message " + msg.what); + } + if (isMessageHandledUnderWakelock(msg.what)) { + try { + mBrokerEventWakeLock.release(); + } catch (Exception e) { + Log.e(TAG, "Exception releasing wakelock", e); + } + } + } + } + + // List of all messages. If a message has be handled under wakelock, add it to + // the isMessageHandledUnderWakelock(int) method + // Naming of msg indicates arguments, using JNI argument grammar + // (e.g. II indicates two int args, IL indicates int and Obj arg) + private static final int MSG_RESTORE_DEVICES = 1; + private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2; + private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; + private static final int MSG_IIL_SET_FORCE_USE = 4; + private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; + private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6; + private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; + private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; + private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; + private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10; + private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; + private static final int MSG_REPORT_NEW_ROUTES = 13; + private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; + private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; + private static final int MSG_I_DISCONNECT_BT_SCO = 16; + private static final int MSG_TOGGLE_HDMI = 17; + private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; + + + private static boolean isMessageHandledUnderWakelock(int msgId) { + switch(msgId) { + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_TOGGLE_HDMI: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + return true; + default: + return false; + } + } + + // Message helper methods + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private void sendMsg(int msg, int existingMsgPolicy, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay); + } + + private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay); + } + + private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay); + } + + private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay); + } + + private void sendMsgNoDelay(int msg, int existingMsgPolicy) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0); + } + + private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0); + } + + private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0); + } + + private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0); + } + + private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0); + } + + private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0); + } + + private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, + int delay) { + if (existingMsgPolicy == SENDMSG_REPLACE) { + mBrokerHandler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) { + return; + } + + if (isMessageHandledUnderWakelock(msg)) { + final long identity = Binder.clearCallingIdentity(); + try { + mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS); + } catch (Exception e) { + Log.e(TAG, "Exception acquiring wakelock", e); + } + Binder.restoreCallingIdentity(identity); + } + + synchronized (sLastDeviceConnectionMsgTimeLock) { + long time = SystemClock.uptimeMillis() + delay; + + switch (msg) { + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + if (sLastDeviceConnectMsgTime >= time) { + // add a little delay to make sure messages are ordered as expected + time = sLastDeviceConnectMsgTime + 30; + } + sLastDeviceConnectMsgTime = time; + break; + default: + break; + } + + mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj), + time); + } + } + + //------------------------------------------------------------- + // internal utilities + private void sendBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java new file mode 100644 index 000000000000..eb76e6e02edc --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -0,0 +1,1024 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioDevicePort; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPort; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** + * Class to manage the inventory of all connected devices. + * This class is thread-safe. + */ +public final class AudioDeviceInventory { + + private static final String TAG = "AS.AudioDeviceInventory"; + + // Actual list of connected devices + // Key for map created from DeviceInfo.makeDeviceListKey() + private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>(); + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // cache of the address of the last dock the device was connected to + private String mDockAddress; + + // Monitoring of audio routes. Protected by mAudioRoutes. + final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); + final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = + new RemoteCallbackList<IAudioRoutesObserver>(); + + //------------------------------------------------------------ + /** + * Class to store info about connected devices. + * Use makeDeviceListKey() to make a unique key for this list. + */ + private static class DeviceInfo { + final int mDeviceType; + final String mDeviceName; + final String mDeviceAddress; + int mDeviceCodecFormat; + + DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { + mDeviceType = deviceType; + mDeviceName = deviceName; + mDeviceAddress = deviceAddress; + mDeviceCodecFormat = deviceCodecFormat; + } + + @Override + public String toString() { + return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) + + " name:" + mDeviceName + + " addr:" + mDeviceAddress + + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; + } + + /** + * Generate a unique key for the mConnectedDevices List by composing the device "type" + * and the "address" associated with a specific instance of that device type + */ + private static String makeDeviceListKey(int device, String deviceAddress) { + return "0x" + Integer.toHexString(device) + ":" + deviceAddress; + } + } + + /** + * A class just for packaging up a set of connection parameters. + */ + /*package*/ class WiredDeviceConnectionState { + public final int mType; + public final @AudioService.ConnectionState int mState; + public final String mAddress; + public final String mName; + public final String mCaller; + + /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + mType = type; + mState = state; + mAddress = address; + mName = name; + mCaller = caller; + } + } + + //------------------------------------------------------------ + // Message handling from AudioDeviceBroker + + /** + * Restore previously connected devices. Use in case of audio server crash + * (see AudioService.onAudioServerDied() method) + */ + /*package*/ void onRestoreDevices() { + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceInfo di = mConnectedDevices.valueAt(i); + AudioSystem.setDeviceConnectionState( + di.mDeviceType, + AudioSystem.DEVICE_STATE_AVAILABLE, + di.mDeviceAddress, + di.mDeviceName, + di.mDeviceCodecFormat); + } + } + } + + /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, + @AudioService.BtProfileConnectionState int state, int a2dpVolume) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state=" + + state + " is dock=" + btDevice.isBluetoothDock()); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP sink connected: device addr=" + address + " state=" + state)); + + final int a2dpCodec; + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + a2dpCodec = btInfo.getCodec(); + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address, + AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + mDeviceBroker.cancelA2dpDockTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) { + mDeviceBroker.cancelA2dpDockTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress, + AudioSystem.AUDIO_FORMAT_DEFAULT); + } + } + if (a2dpVolume != -1) { + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onSetA2dpSinkConnectionState"); + mDeviceBroker.setDeviceVolume( + streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + makeA2dpDeviceAvailable(address, btDevice.getName(), + "onSetA2dpSinkConnectionState", a2dpCodec); + } + } + } + + /*package*/ void onSetA2dpSourceConnectionState( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + + state); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcAvailable(address); + } + } + } + + /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice, + @AudioService.BtProfileConnectionState int state) { + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onSetHearingAidConnectionState addr=" + address)); + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceAvailable(address, btDevice.getName(), + "onSetHearingAidConnectionState"); + } + } + } + + /*package*/ void onBluetoothA2dpDeviceConfigChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpDeviceConfigChange addr=" + address)); + + final int a2dpCodec = btInfo.getCodec(); + + synchronized (mConnectedDevices) { + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); + return; + } + // Device is connected + if (di.mDeviceCodecFormat != a2dpCodec) { + di.mDeviceCodecFormat = a2dpCodec; + mConnectedDevices.replace(key, di); + } + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" + + di.mDeviceCodecFormat); + } + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) { + // force A2DP device disconnection in case of error so that AudioService state + // is consistent with audio policy manager state + final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onBluetoothA2dpActiveDeviceChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + int a2dpVolume = btInfo.getVolume(); + final int a2dpCodec = btInfo.getCodec(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpActiveDeviceChange addr=" + address)); + + synchronized (mConnectedDevices) { + //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo + // for this type of message + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + return; + } + + // Device is connected + if (a2dpVolume != -1) { + final AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onBluetoothA2dpActiveDeviceChange"); + mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { + int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + // force A2DP device disconnection in case of error so that AudioService state is + // consistent with audio policy manager state + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow(address, a2dpCodec); + } + } + + /*package*/ void onReportNewRoutes() { + int n = mRoutesObservers.beginBroadcast(); + if (n > 0) { + AudioRoutesInfo routes; + synchronized (mCurAudioRoutes) { + routes = new AudioRoutesInfo(mCurAudioRoutes); + } + while (n > 0) { + n--; + IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); + try { + obs.dispatchAudioRoutesChanged(routes); + } catch (RemoteException e) { } + } + } + mRoutesObservers.finishBroadcast(); + mDeviceBroker.observeDevicesForAllStreams(); + } + + private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB; + + /*package*/ void onSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState wdcs) { + AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); + + synchronized (mConnectedDevices) { + if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) + && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { + mDeviceBroker.setBluetoothA2dpOnInt(true, + "onSetWiredDeviceConnectionState state DISCONNECTED"); + } + + if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress, + wdcs.mName)) { + // change of connection state failed, bailout + return; + } + if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { + if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { + mDeviceBroker.setBluetoothA2dpOnInt(false, + "onSetWiredDeviceConnectionState state not DISCONNECTED"); + } + mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller); + } + mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); + sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName); + updateAudioRoutes(wdcs.mType, wdcs.mState); + } + } + + /*package*/ void onToggleHdmi() { + synchronized (mConnectedDevices) { + // Is HDMI connected? + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); + return; + } + // Toggle HDMI to retrigger broadcast with proper formats. + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", + "android"); // disconnect + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_AVAILABLE, "", "", + "android"); // reconnect + } + } + //------------------------------------------------------------ + // + + /** + * Implements the communication with AudioSystem to (dis)connect a device in the native layers + * @param connect true if connection + * @param device the device type + * @param address the address of the device + * @param deviceName human-readable name of device + * @return false if an error was reported by AudioSystem + */ + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + + Integer.toHexString(device) + " address:" + address + + " name:" + deviceName + ")"); + } + synchronized (mConnectedDevices) { + final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceKey:" + deviceKey); + } + DeviceInfo di = mConnectedDevices.get(deviceKey); + boolean isConnected = di != null; + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); + } + if (connect && !isConnected) { + final int res = AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + + " due to command error " + res); + return false; + } + mConnectedDevices.put(deviceKey, new DeviceInfo( + device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(device); + return true; + } else if (!connect && isConnected) { + AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + // always remove even if disconnection failed + mConnectedDevices.remove(deviceKey); + return true; + } + Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + + ", deviceSpec=" + di + ", connect=" + connect); + } + return false; + } + + + /*package*/ void disconnectA2dp() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + makeA2dpDeviceUnavailableLater(deviceAddress, delay) + ); + } + } + } + } + + /*package*/ void disconnectA2dpSink() { + synchronized (mConnectedDevices) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); + } + } + + /*package*/ void disconnectHearingAid() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mHearingAidLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_HEARING_AID devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + // TODO delay not used? + makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) + ); + } + } + } + } + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { + synchronized (mConnectedDevices) { + return checkSendBecomingNoisyIntentInt(device, state, musicDevice); + } + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + synchronized (mCurAudioRoutes) { + AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); + mRoutesObservers.register(observer); + return routes; + } + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mCurAudioRoutes; + } + + /*package*/ int setBluetoothA2dpDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { + int delay; + if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { + throw new IllegalArgumentException("invalid profile " + profile); + } + synchronized (mConnectedDevices) { + if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { + int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + intState, musicDevice); + } else { + delay = 0; + } + + final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device + + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec + + " suppressNoisyIntent: " + suppressNoisyIntent); + } + + final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo = + new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec); + if (profile == BluetoothProfile.A2DP) { + mDeviceBroker.postA2dpSinkConnection(state, + a2dpDeviceInfo, + delay); + } else { //profile == BluetoothProfile.A2DP_SINK + mDeviceBroker.postA2dpSourceConnection(state, + a2dpDeviceInfo, + delay); + } + } + return delay; + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + return setBluetoothA2dpDeviceConnectionState(device, state, profile, + suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + // state == BluetoothProfile.STATE_CONNECTED + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i); + if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + continue; + } + // If A2DP device exists, this is either an active device change or + // device config change + final String existingDevicekey = mConnectedDevices.keyAt(i); + final String deviceName = device.getName(); + final String address = device.getAddress(); + final String newDeviceKey = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + // Device not equal to existing device, active device change + if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { + mConnectedDevices.remove(existingDevicekey); + mConnectedDevices.put(newDeviceKey, new DeviceInfo( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, + address, a2dpCodec)); + mDeviceBroker.postA2dpActiveDeviceChange( + new BtHelper.BluetoothA2dpDeviceInfo( + device, a2dpVolume, a2dpCodec)); + return 0; + } else { + // Device config change for existing device + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); + return 0; + } + } + } + return 0; + } + + /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + synchronized (mConnectedDevices) { + int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); + mDeviceBroker.postSetWiredDeviceConnectionState( + new WiredDeviceConnectionState(type, state, address, name, caller), + delay); + return delay; + } + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { + int delay; + synchronized (mConnectedDevices) { + if (!suppressNoisyIntent) { + int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID, + intState, musicDevice); + } else { + delay = 0; + } + mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); + return delay; + } + } + + + //------------------------------------------------------------------- + // Internal utilities + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceAvailable(String address, String name, String eventSource, + int a2dpCodec) { + // enable A2DP before notifying A2DP connection to avoid unnecessary processing in + // audio policy manager + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); + // Reset A2DP suspend state each time a new sink is connected + AudioSystem.setParameters("A2dpSuspended=false"); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, + address, a2dpCodec)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + if (address == null) { + return; + } + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + // Remove A2DP routes as well + setCurrentAudioRouteNameIfPossible(null); + if (mDockAddress == address) { + mDockAddress = null; + } + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { + // prevent any activity on the A2DP audio output to avoid unwanted + // reconnection of the sink. + AudioSystem.setParameters("A2dpSuspended=true"); + // retrieve DeviceInfo before removing device + final String deviceKey = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); + final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : + AudioSystem.AUDIO_FORMAT_DEFAULT; + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(deviceKey); + // send the delayed message to make the device unavailable later + mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs); + } + + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcAvailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { + final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC) + .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC); + + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.setDeviceVolume( + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC), + AudioSystem.DEVICE_OUT_HEARING_AID); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); + // Remove Hearing Aid routes as well + setCurrentAudioRouteNameIfPossible(null); + } + + @GuardedBy("mConnectedDevices") + private void setCurrentAudioRouteNameIfPossible(String name) { + synchronized (mCurAudioRoutes) { + if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { + return; + } + if (name != null || !isCurrentDeviceConnected()) { + mCurAudioRoutes.bluetoothName = name; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + @GuardedBy("mConnectedDevices") + private boolean isCurrentDeviceConnected() { + return mConnectedDevices.values().stream().anyMatch(deviceInfo -> + TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); + } + + // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only + // sent if: + // - none of these devices are connected anymore after one is disconnected AND + // - the device being disconnected is actually used for music. + // Access synchronized on mConnectedDevices + private int mBecomingNoisyIntentDevices = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI + | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE + | AudioSystem.DEVICE_OUT_HEARING_AID; + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + @GuardedBy("mConnectedDevices") + private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) { + if (state != 0) { + return 0; + } + if ((device & mBecomingNoisyIntentDevices) == 0) { + return 0; + } + int delay = 0; + int devices = 0; + for (int i = 0; i < mConnectedDevices.size(); i++) { + int dev = mConnectedDevices.valueAt(i).mDeviceType; + if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) + && ((dev & mBecomingNoisyIntentDevices) != 0)) { + devices |= dev; + } + } + if (musicDevice == AudioSystem.DEVICE_NONE) { + musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + } + // ignore condition on device being actually used for music when in communication + // because music routing is altered in this case. + // also checks whether media routing if affected by a dynamic policy + if (((device == musicDevice) || mDeviceBroker.isInCommunication()) + && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) { + mDeviceBroker.broadcastBecomingNoisy(); + delay = 1000; + } + + return delay; + } + + // Intent "extra" data keys. + private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; + private static final String CONNECT_INTENT_KEY_STATE = "state"; + private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; + private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; + private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; + private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; + private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; + + private void sendDeviceConnectionIntent(int device, int state, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + + " state:0x" + Integer.toHexString(state) + " address:" + address + + " name:" + deviceName + ");"); + } + Intent intent = new Intent(); + + switch(device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 0); + break; + case AudioSystem.DEVICE_OUT_USB_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", + AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); + break; + case AudioSystem.DEVICE_IN_USB_HEADSET: + if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE) { + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + } else { + // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing + return; + } + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + configureHdmiPlugIntent(intent, state); + break; + } + + if (intent.getAction() == null) { + return; + } + + intent.putExtra(CONNECT_INTENT_KEY_STATE, state); + intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); + intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); + + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateAudioRoutes(int device, int state) { + int connType = 0; + + switch (device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + connType = AudioRoutesInfo.MAIN_HEADSET; + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + connType = AudioRoutesInfo.MAIN_HEADPHONES; + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + connType = AudioRoutesInfo.MAIN_HDMI; + break; + case AudioSystem.DEVICE_OUT_USB_DEVICE: + case AudioSystem.DEVICE_OUT_USB_HEADSET: + connType = AudioRoutesInfo.MAIN_USB; + break; + } + + synchronized (mCurAudioRoutes) { + if (connType == 0) { + return; + } + int newConn = mCurAudioRoutes.mainType; + if (state != 0) { + newConn |= connType; + } else { + newConn &= ~connType; + } + if (newConn != mCurAudioRoutes.mainType) { + mCurAudioRoutes.mainType = newConn; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { + intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); + intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); + if (state != AudioService.CONNECTION_STATE_CONNECTED) { + return; + } + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + int[] portGeneration = new int[1]; + int status = AudioSystem.listAudioPorts(ports, portGeneration); + if (status != AudioManager.SUCCESS) { + Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); + return; + } + for (AudioPort port : ports) { + if (!(port instanceof AudioDevicePort)) { + continue; + } + final AudioDevicePort devicePort = (AudioDevicePort) port; + if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI + && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) { + continue; + } + // found an HDMI port: format the list of supported encodings + int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); + if (formats.length > 0) { + ArrayList<Integer> encodingList = new ArrayList(1); + for (int format : formats) { + // a format in the list can be 0, skip it + if (format != AudioFormat.ENCODING_INVALID) { + encodingList.add(format); + } + } + final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); + intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); + } + // find the maximum supported number of channels + int maxChannels = 0; + for (int mask : devicePort.channelMasks()) { + int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); + if (channelCount > maxChannels) { + maxChannels = channelCount; + } + } + intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index de389bc3aa01..df33bf249133 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -35,14 +36,9 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IUidObserver; import android.app.NotificationManager; -import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothCodecConfig; -import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -65,14 +61,12 @@ import android.hardware.hdmi.HdmiPlaybackClient; import android.hardware.hdmi.HdmiTvClient; import android.hardware.usb.UsbManager; import android.media.AudioAttributes; -import android.media.AudioDevicePort; import android.media.AudioFocusInfo; import android.media.AudioFocusRequest; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPlaybackConfiguration; -import android.media.AudioPort; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; @@ -104,7 +98,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -120,8 +113,6 @@ import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; @@ -137,10 +128,8 @@ import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.audio.AudioServiceEvents.ForceUseEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; -import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent; import com.android.server.pm.UserManagerService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -150,6 +139,8 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; @@ -175,19 +166,20 @@ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, AccessibilityManager.AccessibilityServicesStateChangeListener { - private static final String TAG = "AudioService"; + private static final String TAG = "AS.AudioService"; /** Debug audio mode */ - protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG); + protected static final boolean DEBUG_MODE = false; /** Debug audio policy feature */ - protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG); + protected static final boolean DEBUG_AP = false; /** Debug volumes */ - protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG); + protected static final boolean DEBUG_VOL = false; /** debug calls to devices APIs */ - protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG); + protected static final boolean DEBUG_DEVICES = false; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -213,11 +205,11 @@ public class AudioService extends IAudioService.Stub return mPlatformType == AudioSystem.PLATFORM_VOICE; } - private boolean isPlatformTelevision() { + /*package*/ boolean isPlatformTelevision() { return mPlatformType == AudioSystem.PLATFORM_TELEVISION; } - private boolean isPlatformAutomotive() { + /*package*/ boolean isPlatformAutomotive() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } @@ -242,52 +234,40 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_FORCE_USE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; private static final int MSG_SET_ALL_VOLUMES = 10; - private static final int MSG_REPORT_NEW_ROUTES = 12; - private static final int MSG_SET_FORCE_BT_A2DP_USE = 13; - private static final int MSG_CHECK_MUSIC_ACTIVE = 14; - private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18; - private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19; - private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; - private static final int MSG_SYSTEM_READY = 21; - private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; - private static final int MSG_UNMUTE_STREAM = 24; - private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25; - private static final int MSG_INDICATE_SYSTEM_READY = 26; - private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27; - private static final int MSG_NOTIFY_VOL_EVENT = 28; - private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29; - private static final int MSG_ENABLE_SURROUND_FORMATS = 30; + private static final int MSG_CHECK_MUSIC_ACTIVE = 11; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13; + private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14; + private static final int MSG_UNLOAD_SOUND_EFFECTS = 15; + private static final int MSG_SYSTEM_READY = 16; + private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17; + private static final int MSG_UNMUTE_STREAM = 18; + private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19; + private static final int MSG_INDICATE_SYSTEM_READY = 20; + private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21; + private static final int MSG_NOTIFY_VOL_EVENT = 22; + private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23; + private static final int MSG_ENABLE_SURROUND_FORMATS = 24; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100; - private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101; - private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102; - private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103; - private static final int MSG_DISABLE_AUDIO_FOR_UID = 104; - private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105; - private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106; - private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107; + private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; // end of messages handled under wakelock - private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; - // Timeout for connection to bluetooth headset service - private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; - // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; - private static final int BT_HEARING_AID_GAIN_MIN = -128; - /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ private AudioHandler mAudioHandler; /** @see VolumeStreamState */ private VolumeStreamState[] mStreamStates; + + /*package*/ VolumeStreamState getStreamState(int stream) { + return mStreamStates[stream]; + } + private SettingsObserver mSettingsObserver; private int mMode = AudioSystem.MODE_NORMAL; @@ -477,135 +457,13 @@ public class AudioService extends IAudioService.Stub private final UserRestrictionsListener mUserRestrictionsListener = new AudioServiceUserRestrictionsListener(); - // Devices currently connected - // Use makeDeviceListKey() to make a unique key for this list. - private class DeviceListSpec { - int mDeviceType; - String mDeviceName; - String mDeviceAddress; - int mDeviceCodecFormat; - - DeviceListSpec(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat) { - mDeviceType = deviceType; - mDeviceName = deviceName; - mDeviceAddress = deviceAddress; - mDeviceCodecFormat = deviceCodecFormat; - } - - public String toString() { - return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName - + " address:" + mDeviceAddress - + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; - } - } - - // Generate a unique key for the mConnectedDevices List by composing the device "type" - // and the "address" associated with a specific instance of that device type - private String makeDeviceListKey(int device, String deviceAddress) { - return "0x" + Integer.toHexString(device) + ":" + deviceAddress; - } - - private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>(); - - private class BluetoothA2dpDeviceInfo { - BluetoothDevice mBtDevice; - int mVolume; - int mCodec; - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) { - this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); - } - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice, - int volume, int codec) { - mBtDevice = btDevice; - mVolume = volume; - mCodec = codec; - } - - public BluetoothDevice getBtDevice() { - return mBtDevice; - } - - public int getVolume() { - return mVolume; - } - - public int getCodec() { - return mCodec; - } - } - - private int mapBluetoothCodecToAudioFormat(int btCodecType) { - switch (btCodecType) { - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: - return AudioSystem.AUDIO_FORMAT_SBC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: - return AudioSystem.AUDIO_FORMAT_AAC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: - return AudioSystem.AUDIO_FORMAT_APTX; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: - return AudioSystem.AUDIO_FORMAT_APTX_HD; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: - return AudioSystem.AUDIO_FORMAT_LDAC; - default: - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - } - - // Forced device usage for communications - private int mForcedUseForComm; - private int mForcedUseForCommExt; // External state returned by getters: always consistent - // with requests by setters - // List of binder death handlers for setMode() client processes. // The last process to have called setMode() is at the top of the list. - private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); - - // List of clients having issued a SCO start request - private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); - - // BluetoothHeadset API to control SCO connection - private BluetoothHeadset mBluetoothHeadset; - - // Bluetooth headset device - private BluetoothDevice mBluetoothHeadsetDevice; - - // Indicate if SCO audio connection is currently active and if the initiator is - // audio service (internal) or bluetooth headset (external) - private int mScoAudioState; - // SCO audio state is not active - private static final int SCO_STATE_INACTIVE = 0; - // SCO audio activation request waiting for headset service to connect - private static final int SCO_STATE_ACTIVATE_REQ = 1; - // SCO audio state is active or starting due to a request from AudioManager API - private static final int SCO_STATE_ACTIVE_INTERNAL = 3; - // SCO audio deactivation request waiting for headset service to connect - private static final int SCO_STATE_DEACTIVATE_REQ = 4; - // SCO audio deactivation in progress, waiting for Bluetooth audio intent - private static final int SCO_STATE_DEACTIVATING = 5; - - // SCO audio state is active due to an action in BT handsfree (either voice recognition or - // in call audio) - private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; - - // Indicates the mode used for SCO audio connection. The mode is virtual call if the request - // originated from an app targeting an API version before JB MR2 and raw audio after that. - private int mScoAudioMode; - // SCO audio mode is undefined - private static final int SCO_MODE_UNDEFINED = -1; - // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) - private static final int SCO_MODE_VIRTUAL_CALL = 0; - // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) - private static final int SCO_MODE_RAW = 1; - // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) - private static final int SCO_MODE_VR = 2; - - private static final int SCO_MODE_MAX = 2; - - // Current connection state indicated by bluetooth headset - private int mScoConnectionState; + // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers + //TODO candidate to be moved to separate class that handles synchronization + @GuardedBy("mDeviceBroker.mSetModeLock") + /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers = + new ArrayList<SetModeDeathHandler>(); // true if boot sequence has been completed private boolean mSystemReady; @@ -636,15 +494,6 @@ public class AudioService extends IAudioService.Stub // Used to play ringtones outside system_server private volatile IRingtonePlayer mRingtonePlayer; - // Request to override default use of A2DP for media. - private boolean mBluetoothA2dpEnabled; - private final Object mBluetoothA2dpEnabledLock = new Object(); - - // Monitoring of audio routes. Protected by mCurAudioRoutes. - final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); - final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers - = new RemoteCallbackList<IAudioRoutesObserver>(); - // Devices for which the volume is fixed and VolumePanel slider should be disabled int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | @@ -669,17 +518,6 @@ public class AudioService extends IAudioService.Stub private final MediaFocusControl mMediaFocusControl; - // Reference to BluetoothA2dp to query for volume. - private BluetoothHearingAid mHearingAid; - // lock always taken synchronized on mConnectedDevices - private final Object mHearingAidLock = new Object(); - // Reference to BluetoothA2dp to query for AbsoluteVolume. - private BluetoothA2dp mA2dp; - // lock always taken synchronized on mConnectedDevices - private final Object mA2dpAvrcpLock = new Object(); - // If absolute volume is supported in AVRCP device - private boolean mAvrcpAbsVolSupported = false; - // Pre-scale for Bluetooth Absolute Volume private float[] mPrescaleAbsoluteVolume = new float[] { 0.5f, // Pre-scale for index 1 @@ -687,8 +525,6 @@ public class AudioService extends IAudioService.Stub 0.85f, // Pre-scale for index 3 }; - private static Long mLastDeviceConnectMsgTime = new Long(0); - private NotificationManager mNm; private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; @@ -705,15 +541,6 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private int mAssistantUid; - // Intent "extra" data keys. - public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; - public static final String CONNECT_INTENT_KEY_STATE = "state"; - public static final String CONNECT_INTENT_KEY_ADDRESS = "address"; - public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; - public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; - public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; - public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; - // Defines the format for the connection "address" for ALSA devices public static String makeAlsaAddressString(int card, int device) { return "card=" + card + ";device=" + device + ";"; @@ -858,8 +685,6 @@ public class AudioService extends IAudioService.Stub sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); - mForcedUseForComm = AudioSystem.FORCE_NONE; - createAudioSystemThread(); AudioSystem.setErrorCallback(mAudioSystemCallback); @@ -886,6 +711,8 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + mDeviceBroker = new AudioDeviceBroker(mContext, this); + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] // array initialized by updateStreamVolumeAlias() updateStreamVolumeAlias(false /*updateVolumes*/, TAG); @@ -988,23 +815,7 @@ public class AudioService extends IAudioService.Stub sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); - mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR; - resetBluetoothSco(); - getBluetoothHeadset(); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - sendStickyBroadcastToAll(newIntent); - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.A2DP); - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEARING_AID); - } + mDeviceBroker.onSystemReady(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { synchronized (mHdmiClientLock) { @@ -1065,39 +876,22 @@ public class AudioService extends IAudioService.Stub readAndSetLowRamDevice(); - // Restore device connection states - synchronized (mConnectedDevices) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec spec = mConnectedDevices.valueAt(i); - AudioSystem.setDeviceConnectionState( - spec.mDeviceType, - AudioSystem.DEVICE_STATE_AVAILABLE, - spec.mDeviceAddress, - spec.mDeviceName, - spec.mDeviceCodecFormat); - } - } + // Restore device connection states, BT state + mDeviceBroker.onAudioServerDied(); + // Restore call state if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) { mModeLogger.log(new AudioEventLogger.StringEvent( "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")")); } - // Restore forced usage for communications and record - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm); - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm); final int forSys; synchronized (mSettingsLock) { forSys = mCameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE; } - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys); + + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied"); // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1120,20 +914,10 @@ public class AudioService extends IAudioService.Stub RotationHelper.updateOrientation(); } - synchronized (mBluetoothA2dpEnabledLock) { - final int forMed = mBluetoothA2dpEnabled ? - AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed); - } - synchronized (mSettingsLock) { final int forDock = mDockAudioMediaEnabled ? AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); updateAssistantUId(true); @@ -1209,6 +993,45 @@ public class AudioService extends IAudioService.Stub } } + /** + * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected. + */ + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + if (state != 0) { + // DEVICE_OUT_HDMI is now connected + if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + + if (isPlatformTelevision()) { + mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; + checkAllFixedVolumeDevices(); + synchronized (mHdmiClientLock) { + if (mHdmiManager != null && mHdmiPlaybackClient != null) { + mHdmiCecSink = false; + mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); + } + } + } + sendEnabledSurroundFormats(mContentResolver, true); + } else { + // DEVICE_OUT_HDMI disconnected + if (isPlatformTelevision()) { + synchronized (mHdmiClientLock) { + if (mHdmiManager != null) { + mHdmiCecSink = false; + } + } + } + } + } + private void checkAllFixedVolumeDevices() { int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1373,7 +1196,7 @@ public class AudioService extends IAudioService.Stub private void sendEncodedSurroundMode(ContentResolver cr, String eventSource) { - int encodedSurroundMode = Settings.Global.getInt( + final int encodedSurroundMode = Settings.Global.getInt( cr, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); sendEncodedSurroundMode(encodedSurroundMode, eventSource); @@ -1402,13 +1225,8 @@ public class AudioService extends IAudioService.Stub break; } if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) { - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_ENCODED_SURROUND, - forceSetting, - eventSource, - 0); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting, + eventSource); } } @@ -1632,7 +1450,7 @@ public class AudioService extends IAudioService.Stub + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream + ", userSelect=" + mUserSelectedVolumeControlStream); - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append("/").append(caller).append(" uid:").append(uid).toString())); final int streamType; @@ -1690,7 +1508,7 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -1871,16 +1689,18 @@ public class AudioService extends IAudioService.Stub if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(newIndex / 10); - } + if (DEBUG_VOL) { + Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + + newIndex + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex); } // Check if volume update should be send to Hearing Aid if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(newIndex, streamType); + Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); } // Check if volume update should be sent to Hdmi system audio. @@ -2052,7 +1872,7 @@ public class AudioService extends IAudioService.Stub + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -2127,18 +1947,20 @@ public class AudioService extends IAudioService.Stub index = rescaleIndex(index * 10, streamType, streamTypeAlias); - if (streamTypeAlias == AudioSystem.STREAM_MUSIC && - (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(index / 10); - } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC + && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 + && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index + + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(index, streamType); + Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { @@ -2881,6 +2703,10 @@ public class AudioService extends IAudioService.Stub } } + /*package*/ void setUpdateRingerModeServiceInt() { + setRingerModeInt(getRingerModeInternal(), false); + } + /** @see AudioManager#shouldVibrate(int) */ public boolean shouldVibrate(int vibrateType) { if (!mHasVibrator) return false; @@ -2921,7 +2747,7 @@ public class AudioService extends IAudioService.Stub } - private class SetModeDeathHandler implements IBinder.DeathRecipient { + /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mPid; private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client @@ -2934,7 +2760,7 @@ public class AudioService extends IAudioService.Stub public void binderDied() { int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); @@ -2949,9 +2775,7 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - final long ident = Binder.clearCallingIdentity(); - disconnectBluetoothSco(newModeOwnerPid); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } @@ -2994,7 +2818,7 @@ public class AudioService extends IAudioService.Stub int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } @@ -3006,11 +2830,11 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - disconnectBluetoothSco(newModeOwnerPid); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } - // must be called synchronized on mSetModeDeathHandlers + // must be called synchronized on mSetModeLock // setModeInt() returns a valid PID if the audio mode was successfully set to // any mode other than NORMAL. private int setModeInt(int mode, IBinder cb, int pid, String caller) { @@ -3380,26 +3204,12 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - if (on) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, - eventSource, 0); - } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){ - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - - mForcedUseForCommExt = mForcedUseForComm; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); + mDeviceBroker.setSpeakerphoneOn(on, eventSource); } /** @see AudioManager#isSpeakerphoneOn() */ public boolean isSpeakerphoneOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + return mDeviceBroker.isSpeakerphoneOn(); } /** @see AudioManager#setBluetoothScoOn(boolean) */ @@ -3410,7 +3220,7 @@ public class AudioService extends IAudioService.Stub // Only enable calls from system components if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { - mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + mDeviceBroker.setBluetoothScoOnByApp(on); return; } @@ -3418,95 +3228,57 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - setBluetoothScoOnInt(on, eventSource); - } - - public void setBluetoothScoOnInt(boolean on, String eventSource) { - Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); - if (on) { - // do not accept SCO ON if SCO audio is not connected - synchronized (mScoClients) { - if ((mBluetoothHeadset != null) - && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { - mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; - Log.w(TAG, "setBluetoothScoOnInt(true) failed because " - + mBluetoothHeadsetDevice + " is not in audio connected mode"); - return; - } - } - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - mForcedUseForCommExt = mForcedUseForComm; - AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off")); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0); - // Un-mute ringtone stream volume - setRingerModeInt(getRingerModeInternal(), false); + + mDeviceBroker.setBluetoothScoOn(on, eventSource); } - /** @see AudioManager#isBluetoothScoOn() */ + /** @see AudioManager#isBluetoothScoOn() + * Note that it doesn't report internal state, but state seen by apps (which may have + * called setBluetoothScoOn() */ public boolean isBluetoothScoOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO); + return mDeviceBroker.isBluetoothScoOnForApp(); } + // TODO investigate internal users due to deprecation of SDK API /** @see AudioManager#setBluetoothA2dpOn(boolean) */ public void setBluetoothA2dpOn(boolean on) { // for logging only final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - synchronized (mBluetoothA2dpEnabledLock) { - if (mBluetoothA2dpEnabled == on) { - return; - } - mBluetoothA2dpEnabled = on; - sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource, 0); - } + mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource); } /** @see AudioManager#isBluetoothA2dpOn() */ public boolean isBluetoothA2dpOn() { - synchronized (mBluetoothA2dpEnabledLock) { - return mBluetoothA2dpEnabled; - } + return mDeviceBroker.isBluetoothA2dpOn(); } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb, int targetSdkVersion) { - int scoAudioMode = + final int scoAudioMode = (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ? - SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED; - startBluetoothScoInt(cb, scoAudioMode); + BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED; + final String eventSource = new StringBuilder("startBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, scoAudioMode, eventSource); } /** @see AudioManager#startBluetoothScoVirtualCall() */ public void startBluetoothScoVirtualCall(IBinder cb) { - startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL); + final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int scoAudioMode){ + void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { if (!checkAudioSettingsPermission("startBluetoothSco()") || !mSystemReady) { return; } - ScoClient client = getScoClient(cb, true); - // The calling identity must be cleared before calling ScoClient.incCount(). - // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - // The caller identity must be cleared after getScoClient() because it is needed if a new - // client is created. - final long ident = Binder.clearCallingIdentity(); - client.incCount(scoAudioMode); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); } /** @see AudioManager#stopBluetoothSco() */ @@ -3515,648 +3287,15 @@ public class AudioService extends IAudioService.Stub !mSystemReady) { return; } - ScoClient client = getScoClient(cb, false); - // The calling identity must be cleared before calling ScoClient.decCount(). - // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - final long ident = Binder.clearCallingIdentity(); - if (client != null) { - client.decCount(); - } - Binder.restoreCallingIdentity(ident); - } - - - private class ScoClient implements IBinder.DeathRecipient { - private IBinder mCb; // To be notified of client's death - private int mCreatorPid; - private int mStartcount; // number of SCO connections started by this client - - ScoClient(IBinder cb) { - mCb = cb; - mCreatorPid = Binder.getCallingPid(); - mStartcount = 0; - } - - public void binderDied() { - synchronized(mScoClients) { - Log.w(TAG, "SCO client died"); - int index = mScoClients.indexOf(this); - if (index < 0) { - Log.w(TAG, "unregistered SCO client died"); - } else { - clearCount(true); - mScoClients.remove(this); - } - } - } - - public void incCount(int scoAudioMode) { - synchronized(mScoClients) { - requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); - if (mStartcount == 0) { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); - } - } - mStartcount++; - } - } - - public void decCount() { - synchronized(mScoClients) { - if (mStartcount == 0) { - Log.w(TAG, "ScoClient.decCount() already 0"); - } else { - mStartcount--; - if (mStartcount == 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "decCount() going to 0 but not registered to binder"); - } - } - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public void clearCount(boolean stopSco) { - synchronized(mScoClients) { - if (mStartcount != 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder"); - } - } - mStartcount = 0; - if (stopSco) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public int getCount() { - return mStartcount; - } - - public IBinder getBinder() { - return mCb; - } - - public int getPid() { - return mCreatorPid; - } - - public int totalCount() { - synchronized(mScoClients) { - int count = 0; - for (ScoClient mScoClient : mScoClients) { - count += mScoClient.getCount(); - } - return count; - } - } - - private void requestScoState(int state, int scoAudioMode) { - checkScoAudioState(); - int clientCount = totalCount(); - if (clientCount != 0) { - Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode - + ", clientCount=" + clientCount); - return; - } - if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - // Make sure that the state transitions to CONNECTING even if we cannot initiate - // the connection. - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); - // Accept SCO audio activation only in NORMAL audio mode or if the mode is - // currently controlled by the same client process. - synchronized(mSetModeDeathHandlers) { - int modeOwnerPid = mSetModeDeathHandlers.isEmpty() - ? 0 : mSetModeDeathHandlers.get(0).getPid(); - if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { - Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " - + modeOwnerPid + " != creatorPid " + mCreatorPid); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return; - } - switch (mScoAudioState) { - case SCO_STATE_INACTIVE: - mScoAudioMode = scoAudioMode; - if (scoAudioMode == SCO_MODE_UNDEFINED) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - if (mBluetoothHeadsetDevice != null) { - mScoAudioMode = Settings.Global.getInt(mContentResolver, - "bluetooth_sco_channel_" - + mBluetoothHeadsetDevice.getAddress(), - SCO_MODE_VIRTUAL_CALL); - if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - } - } - } - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " connection, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - Log.w(TAG, "requestScoState: no active device while connecting," - + " mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } else { - Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice - + " failed, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_DEACTIVATING: - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - break; - case SCO_STATE_DEACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to connect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - - } - } - } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVE_INTERNAL: - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_DEACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " disconnection, mScoAudioMode=" + mScoAudioMode); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } else { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to disconnect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - } - } - } - - private void checkScoAudioState() { - synchronized (mScoClients) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && - mScoAudioState == SCO_STATE_INACTIVE && - mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - } - } - - - private ScoClient getScoClient(IBinder cb, boolean create) { - synchronized(mScoClients) { - for (ScoClient existingClient : mScoClients) { - if (existingClient.getBinder() == cb) { - return existingClient; - } - } - if (create) { - ScoClient newClient = new ScoClient(cb); - mScoClients.add(newClient); - return newClient; - } - return null; - } - } - - public void clearAllScoClients(int exceptPid, boolean stopSco) { - synchronized(mScoClients) { - ScoClient savedClient = null; - for (ScoClient cl : mScoClients) { - if (cl.getPid() != exceptPid) { - cl.clearCount(stopSco); - } else { - savedClient = cl; - } - } - mScoClients.clear(); - if (savedClient != null) { - mScoClients.add(savedClient); - } - } - } - - private boolean getBluetoothHeadset() { - boolean result = false; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - } - // If we could not get a bluetooth headset proxy, send a failure message - // without delay to reset the SCO audio state and clear SCO clients. - // If we could get a proxy, send a delayed failure message that will reset our state - // in case we don't receive onServiceConnected(). - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, - SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0); - return result; - } - - /** - * Disconnect all SCO connections started by {@link AudioManager} except those started by - * {@param exceptPid} - * - * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept - */ - private void disconnectBluetoothSco(int exceptPid) { - synchronized(mScoClients) { - checkScoAudioState(); - if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { - return; - } - clearAllScoClients(exceptPid, true); - } - } - - private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.disconnectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.stopVoiceRecognition(device); - default: - return false; - } - } - - private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.connectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.startScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.startVoiceRecognition(device); - default: - return false; - } - } - - private void resetBluetoothSco() { - synchronized(mScoClients) { - clearAllScoClients(0, false); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - AudioSystem.setParameters("A2dpSuspended=false"); - setBluetoothScoOnInt(false, "resetBluetoothSco"); - } - - private void broadcastScoConnectionState(int state) { - sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE, - SENDMSG_QUEUE, state, 0, null, 0); - } - - private void onBroadcastScoConnectionState(int state) { - if (state != mScoConnectionState) { - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, - mScoConnectionState); - sendStickyBroadcastToAll(newIntent); - mScoConnectionState = state; - } - } - - private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { - if (btDevice == null) { - return true; - } - String address = btDevice.getAddress(); - BluetoothClass btClass = btDevice.getBluetoothClass(); - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - int[] outDeviceTypes = { - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; - if (btClass != null) { - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; - break; - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; - break; - } - } - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - String btDeviceName = btDevice.getName(); - boolean result = false; - if (isActive) { - result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName); - } else { - for (int outDeviceType : outDeviceTypes) { - result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName); - } - } - // handleDeviceConnection() && result to make sure the method get executed - result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result; - return result; - } - - private void setBtScoActiveDevice(BluetoothDevice btDevice) { - synchronized (mScoClients) { - Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); - final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; - if (!Objects.equals(btDevice, previousActiveDevice)) { - if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { - Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " - + previousActiveDevice); - } - if (!handleBtScoActiveDeviceChange(btDevice, true)) { - Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); - // set mBluetoothHeadsetDevice to null when failing to add new device - btDevice = null; - } - mBluetoothHeadsetDevice = btDevice; - if (mBluetoothHeadsetDevice == null) { - resetBluetoothSco(); - } - } - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - BluetoothDevice btDevice; - List<BluetoothDevice> deviceList; - switch(profile) { - case BluetoothProfile.A2DP: - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - mA2dp = (BluetoothA2dp) proxy; - deviceList = mA2dp.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mA2dp.getConnectionState(btDevice); - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, - AudioSystem.DEVICE_NONE); - final String addr = btDevice == null ? "null" : btDevice.getAddress(); - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2DP service connected: device addr=" + addr - + " state=" + state)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SINK_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - delay); - } - } - } - break; - - case BluetoothProfile.A2DP_SINK: - deviceList = proxy.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - synchronized (mConnectedDevices) { - int state = proxy.getConnectionState(btDevice); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SRC_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - 0 /* delay */); - } - } - break; - - case BluetoothProfile.HEADSET: - synchronized (mScoClients) { - // Discard timeout message - mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); - mBluetoothHeadset = (BluetoothHeadset) proxy; - setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); - // Refresh SCO audio state - checkScoAudioState(); - // Continue pending action if any - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { - boolean status = false; - if (mBluetoothHeadsetDevice != null) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - status = connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } - break; - case SCO_STATE_DEACTIVATE_REQ: - status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } - break; - } - } - if (!status) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - } - } - break; - - case BluetoothProfile.HEARING_AID: - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - mHearingAid = (BluetoothHearingAid) proxy; - deviceList = mHearingAid.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mHearingAid.getConnectionState(btDevice); - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_HEARING_AID, intState, - AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - btDevice, - delay); - } - } - } - - break; - - default: - break; - } - } - public void onServiceDisconnected(int profile) { - - switch (profile) { - case BluetoothProfile.A2DP: - disconnectA2dp(); - break; - - case BluetoothProfile.A2DP_SINK: - disconnectA2dpSink(); - break; - - case BluetoothProfile.HEADSET: - disconnectHeadset(); - break; - - case BluetoothProfile.HEARING_AID: - disconnectHearingAid(); - break; - - default: - break; - } - } - }; - - void disconnectAllBluetoothProfiles() { - disconnectA2dp(); - disconnectA2dpSink(); - disconnectHeadset(); - disconnectHearingAid(); - } - - void disconnectA2dp() { - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay); - } - } - } - } - } - - void disconnectA2dpSink() { - synchronized (mConnectedDevices) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices - for(int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpSrcUnavailable(toRemove.valueAt(i)); - } - } - } + final String eventSource = new StringBuilder("stopBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); } - void disconnectHeadset() { - synchronized (mScoClients) { - setBtScoActiveDevice(null); - mBluetoothHeadset = null; - } - } - void disconnectHearingAid() { - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_HEARING_AID devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/); - } - } - } - } + /*package*/ ContentResolver getContentResolver() { + return mContentResolver; } private void onCheckMusicActive(String caller) { @@ -4173,8 +3312,8 @@ public class AudioService extends IAudioService.Stub caller, MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); - if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && - (index > safeMediaVolumeIndex(device))) { + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) + && (index > safeMediaVolumeIndex(device))) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { @@ -4192,8 +3331,7 @@ public class AudioService extends IAudioService.Stub mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } - private int getSafeUsbMediaVolumeIndex() - { + private int getSafeUsbMediaVolumeIndex() { // determine UI volume index corresponding to the wanted safe gain in dBFS int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; @@ -4201,7 +3339,7 @@ public class AudioService extends IAudioService.Stub mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f; - while (Math.abs(max-min) > 1) { + while (Math.abs(max - min) > 1) { int index = (max + min) / 2; float gainDB = AudioSystem.getStreamVolumeDB( AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); @@ -4518,7 +3656,7 @@ public class AudioService extends IAudioService.Stub || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } - private boolean isInCommunication() { + /*package*/ boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = @@ -4671,25 +3809,9 @@ public class AudioService extends IAudioService.Stub } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { return; } - synchronized (mLastDeviceConnectMsgTime) { - long time = SystemClock.uptimeMillis() + delay; - - if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || - msg == MSG_SET_A2DP_SINK_CONNECTION_STATE || - msg == MSG_SET_HEARING_AID_CONNECTION_STATE || - msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || - msg == MSG_A2DP_DEVICE_CONFIG_CHANGE || - msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE || - msg == MSG_BTA2DP_DOCK_TIMEOUT) { - if (mLastDeviceConnectMsgTime >= time) { - // add a little delay to make sure messages are ordered as expected - time = mLastDeviceConnectMsgTime + 30; - } - mLastDeviceConnectMsgTime = time; - } - handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); - } + final long time = SystemClock.uptimeMillis() + delay; + handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } boolean checkAudioSettingsPermission(String method) { @@ -4704,7 +3826,7 @@ public class AudioService extends IAudioService.Stub return false; } - private int getDeviceForStream(int stream) { + /*package*/ int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: @@ -4749,160 +3871,94 @@ public class AudioService extends IAudioService.Stub } } - private int getA2dpCodec(BluetoothDevice device) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); - if (btCodecStatus == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); - if (btCodecConfig == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); - } + + /*package*/ void observeDevicesForAllStreams() { + observeDevicesForStreams(-1); } - /* - * A class just for packaging up a set of connection parameters. + /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0; + /*package*/ static final int CONNECTION_STATE_CONNECTED = 1; + /** + * The states that can be used with AudioService.setWiredDeviceConnectionState() + * Attention: those values differ from those in BluetoothProfile, follow annotations to + * distinguish between @ConnectionState and @BtProfileConnectionState */ - class WiredDeviceConnectionState { - public final int mType; - public final int mState; - public final String mAddress; - public final String mName; - public final String mCaller; + @IntDef({ + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectionState {} - public WiredDeviceConnectionState(int type, int state, String address, String name, - String caller) { - mType = type; - mState = state; - mAddress = address; - mName = name; - mCaller = caller; - } - } - - public void setWiredDeviceConnectionState(int type, int state, String address, String name, + /** + * see AudioManager.setWiredDeviceConnectionState() + */ + public void setWiredDeviceConnectionState(int type, + @ConnectionState int state, String address, String name, String caller) { - synchronized (mConnectedDevices) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:" - + address + ")"); - } - int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_WIRED_DEVICE_CONNECTION_STATE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new WiredDeviceConnectionState(type, state, address, name, caller), - delay); + if (state != CONNECTION_STATE_CONNECTED + && state != CONNECTION_STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } + mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); } + /** + * @hide + * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState() + * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + @IntDef({ + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileConnectionState {} + public int setBluetoothHearingAidDeviceConnectionState( - BluetoothDevice device, int state, boolean suppressNoisyIntent, - int musicDevice) + @NonNull BluetoothDevice device, @BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { - int delay; - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setHearingAidDeviceConnectionState state=" + state - + " addr=" + device.getAddress() - + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG)); - synchronized (mConnectedDevices) { - if (!suppressNoisyIntent) { - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - intState, musicDevice); - } else { - delay = 0; - } - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - device, - delay); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - return delay; - } - - public int setBluetoothA2dpDeviceConnectionState( - BluetoothDevice device, int state, int profile) - { - return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, false /* suppressNoisyIntent */, - -1 /* a2dpVolume */); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); + } + return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice, "AudioService"); } - public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device, - int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) - { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state - // only querying address as this is the only readily available field on the device - + " addr=" + device.getAddress() - + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent - + " vol=" + a2dpVolume)).printLog(TAG)); - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored")); - return 0; - } - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); - } - - public int setBluetoothA2dpDeviceConnectionStateInt( - BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, - int musicDevice, int a2dpVolume) - { - int delay; - if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { - throw new IllegalArgumentException("invalid profile " + profile); + /** + * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - synchronized (mConnectedDevices) { - if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - intState, musicDevice); - } else { - delay = 0; - } - - int a2dpCodec = getA2dpCodec(device); - - if (DEBUG_DEVICES) { - Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device - + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec - + " suppressNoisyIntent: " + suppressNoisyIntent); - } - - queueMsgUnderWakeLock(mAudioHandler, - (profile == BluetoothProfile.A2DP ? - MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE), - state, - 0, /* arg2 unused */ - new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec), - delay); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); } - return delay; + return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state, + profile, suppressNoisyIntent, a2dpVolume); } + /** + * See AudioManager.handleBluetoothA2dpDeviceConfigChange() + * @param device + */ public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) { - synchronized (mConnectedDevices) { - int a2dpCodec = getA2dpCodec(device); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_DEVICE_CONFIG_CHANGE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec), - 0 /* delay */); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } /** @@ -4912,52 +3968,18 @@ public class AudioService extends IAudioService.Stub public int handleBluetoothA2dpActiveDeviceChange( BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); + } if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { throw new IllegalArgumentException("invalid profile " + profile); } - - synchronized (mConnectedDevices) { - if (state == BluetoothA2dp.STATE_CONNECTED) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - continue; - } - // If A2DP device exists, this is either an active device change or - // device config change - String existingDevicekey = mConnectedDevices.keyAt(i); - String deviceName = device.getName(); - String address = device.getAddress(); - String newDeviceKey = makeDeviceListKey( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - int a2dpCodec = getA2dpCodec(device); - // Device not equal to existing device, active device change - if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { - mConnectedDevices.remove(existingDevicekey); - mConnectedDevices.put(newDeviceKey, new DeviceListSpec( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, - address, a2dpCodec)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_ACTIVE_DEVICE_CHANGE, - 0, - 0, - new BluetoothA2dpDeviceInfo( - device, a2dpVolume, a2dpCodec), - 0 /* delay */); - return 0; - } else { - // Device config change for existing device - handleBluetoothA2dpDeviceConfigChange(device); - return 0; - } - } - } + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } - - // New device connection or a device disconnect - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); + return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); } private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = @@ -4967,24 +3989,27 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; + /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { + sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, + newDevice, 0, null, 0); + } + private void onAccessoryPlugMediaUnmute(int newDevice) { if (DEBUG_VOL) { Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } - synchronized (mConnectedDevices) { - if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS - && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 - && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted - && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 - && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) - { - if (DEBUG_VOL) { - Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", - newDevice, AudioSystem.getOutputDeviceName(newDevice))); - } - mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); + + if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 + && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted + && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 + && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) { + if (DEBUG_VOL) { + Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", + newDevice, AudioSystem.getOutputDeviceName(newDevice))); } + mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); } } @@ -4994,7 +4019,7 @@ public class AudioService extends IAudioService.Stub // NOTE: Locking order for synchronized objects related to volume or ringer mode management: // 1 mScoclient OR mSafeMediaVolumeState - // 2 mSetModeDeathHandlers + // 2 mSetModeLock // 3 mSettingsLock // 4 VolumeStreamState.class public class VolumeStreamState { @@ -5138,11 +4163,11 @@ public class AudioService extends IAudioService.Stub } // must be called while synchronized VolumeStreamState.class - public void applyDeviceVolume_syncVSS(int device) { + /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) { int index; if (mIsMuted) { index = 0; - } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) { + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5151,10 +4176,11 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(device) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } public void applyAllVolumes() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // apply device specific volumes first int index; @@ -5164,7 +4190,7 @@ public class AudioService extends IAudioService.Stub if (mIsMuted) { index = 0; } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - mAvrcpAbsVolSupported) { + isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5173,7 +4199,7 @@ public class AudioService extends IAudioService.Stub } else { index = (mIndexMap.valueAt(i) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } } // apply default volume last: by convention , default device volume will be used @@ -5183,7 +4209,7 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; } - AudioSystem.setStreamVolumeIndex( + AudioSystem.setStreamVolumeIndexAS( mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); } } @@ -5373,6 +4399,7 @@ public class AudioService extends IAudioService.Stub } public void checkFixedVolumeDevices() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // ignore settings for fixed volume devices: volume should always be at max or 0 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) { @@ -5383,7 +4410,7 @@ public class AudioService extends IAudioService.Stub || (((device & mFixedVolumeDevices) != 0) && index != 0)) { mIndexMap.put(device, mIndexMax); } - applyDeviceVolume_syncVSS(device); + applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); } } } @@ -5465,11 +4492,13 @@ public class AudioService extends IAudioService.Stub } } - private void setDeviceVolume(VolumeStreamState streamState, int device) { + /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { + + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // Apply volume - streamState.applyDeviceVolume_syncVSS(device); + streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -5479,11 +4508,13 @@ public class AudioService extends IAudioService.Stub // Make sure volume is also maxed out on A2DP device for aliased stream // that may have a different device selected int streamDevice = getDeviceForStream(streamType); - if ((device != streamDevice) && mAvrcpAbsVolSupported && - ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { - mStreamStates[streamType].applyDeviceVolume_syncVSS(device); + if ((device != streamDevice) && isAvrcpAbsVolSupported + && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume_syncVSS(device, + isAvrcpAbsVolSupported); } - mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice); + mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice, + isAvrcpAbsVolSupported); } } } @@ -5762,12 +4793,6 @@ public class AudioService extends IAudioService.Stub } } - private void setForceUse(int usage, int config, String eventSource) { - synchronized (mConnectedDevices) { - setForceUseInt_SyncDevices(usage, config, eventSource); - } - } - private void onPersistSafeVolumeState(int state) { Settings.Global.putInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, @@ -5834,56 +4859,20 @@ public class AudioService extends IAudioService.Stub onPlaySoundEffect(msg.arg1, msg.arg2); break; - case MSG_BTA2DP_DOCK_TIMEOUT: - // msg.obj == address of BTA2DP device - synchronized (mConnectedDevices) { - makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); - } - mAudioEventWakeLock.release(); - break; - case MSG_SET_FORCE_USE: - case MSG_SET_FORCE_BT_A2DP_USE: - setForceUse(msg.arg1, msg.arg2, (String) msg.obj); - break; - - case MSG_BT_HEADSET_CNCT_FAILED: - resetBluetoothSco(); - break; - - case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: - { WiredDeviceConnectionState connectState = - (WiredDeviceConnectionState)msg.obj; - mDeviceLogger.log(new WiredDevConnectEvent(connectState)); - onSetWiredDeviceConnectionState(connectState.mType, connectState.mState, - connectState.mAddress, connectState.mName, connectState.mCaller); - mAudioEventWakeLock.release(); + { + final String eventSource = (String) msg.obj; + final int useCase = msg.arg1; + final int config = msg.arg2; + if (useCase == AudioSystem.FOR_MEDIA) { + Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from " + + eventSource); + break; } - break; - - case MSG_SET_A2DP_SRC_CONNECTION_STATE: - onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_A2DP_SINK_CONNECTION_STATE: - onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_HEARING_AID_CONNECTION_STATE: - onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_DEVICE_CONFIG_CHANGE: - onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_ACTIVE_DEVICE_CHANGE: - onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); + sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } break; case MSG_DISABLE_AUDIO_FOR_UID: @@ -5892,35 +4881,10 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; - case MSG_REPORT_NEW_ROUTES: { - int N = mRoutesObservers.beginBroadcast(); - if (N > 0) { - AudioRoutesInfo routes; - synchronized (mCurAudioRoutes) { - routes = new AudioRoutesInfo(mCurAudioRoutes); - } - while (N > 0) { - N--; - IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N); - try { - obs.dispatchAudioRoutesChanged(routes); - } catch (RemoteException e) { - } - } - } - mRoutesObservers.finishBroadcast(); - observeDevicesForStreams(-1); - break; - } - case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; - case MSG_BROADCAST_AUDIO_BECOMING_NOISY: - onSendBecomingNoisyIntent(); - break; - case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), @@ -5930,10 +4894,6 @@ public class AudioService extends IAudioService.Stub onPersistSafeVolumeState(msg.arg1); break; - case MSG_BROADCAST_BT_CONNECTION_STATE: - onBroadcastScoConnectionState(msg.arg1); - break; - case MSG_SYSTEM_READY: onSystemReady(); break; @@ -6033,20 +4993,7 @@ public class AudioService extends IAudioService.Stub if (mEncodedSurroundMode != newSurroundMode) { // Send to AudioPolicyManager sendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); - synchronized(mConnectedDevices) { - // Is HDMI connected? - String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); - DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec != null) { - // Toggle HDMI to retrigger broadcast with proper formats. - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", - "android"); // disconnect - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_AVAILABLE, "", "", - "android"); // reconnect - } - } + mDeviceBroker.toggleHdmiIfConnected_Async(); mEncodedSurroundMode = newSurroundMode; mSurroundModeChanged = true; } else { @@ -6055,515 +5002,18 @@ public class AudioService extends IAudioService.Stub } } - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceAvailable( - String address, String name, String eventSource, int a2dpCodec) { - // enable A2DP before notifying A2DP connection to avoid unnecessary processing in - // audio policy manager - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - setBluetoothA2dpOnInt(true, eventSource); - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); - // Reset A2DP suspend state each time a new sink is connected - AudioSystem.setParameters("A2dpSuspended=false"); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, a2dpCodec)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0); - setCurrentAudioRouteNameIfPossible(name); - } - - private void onSendBecomingNoisyIntent() { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); - sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { - if (address == null) { - return; - } - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = false; - } - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); - // Remove A2DP routes as well - setCurrentAudioRouteNameIfPossible(null); - if (mDockAddress == address) { - mDockAddress = null; - } - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { - // prevent any activity on the A2DP audio output to avoid unwanted - // reconnection of the sink. - AudioSystem.setParameters("A2dpSuspended=true"); - // Retrieve deviceSpec before removing device - String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat : - AudioSystem.AUDIO_FORMAT_DEFAULT; - // the device will be made unavailable later, so consider it disconnected right away - mConnectedDevices.remove(deviceKey); - // send the delayed message to make the device unavailable later - queueMsgUnderWakeLock(mAudioHandler, - MSG_BTA2DP_DOCK_TIMEOUT, - a2dpCodec, - 0, - address, - delayMs); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcAvailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); - } - - private void setHearingAidVolume(int index, int streamType) { - synchronized (mHearingAidLock) { - if (mHearingAid != null) { - //hearing aid expect volume value in range -128dB to 0dB - int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10, - AudioSystem.DEVICE_OUT_HEARING_AID); - if (gainDB < BT_HEARING_AID_GAIN_MIN) - gainDB = BT_HEARING_AID_GAIN_MIN; - mHearingAid.setVolume(gainDB); - } - } - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { - int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); - setHearingAidVolume(index, AudioSystem.STREAM_MUSIC); - - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name, - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0); - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, - mStreamStates[AudioSystem.STREAM_MUSIC], 0); - setCurrentAudioRouteNameIfPossible(name); - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); - // Remove Hearing Aid routes as well - setCurrentAudioRouteNameIfPossible(null); - } - - // must be called synchronized on mConnectedDevices - private void cancelA2dpDeviceTimeout() { - mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - // must be called synchronized on mConnectedDevices - private boolean hasScheduledA2dpDockTimeout() { - return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state - + " is dock: " + btDevice.isBluetoothDock()); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat); - } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - cancelA2dpDeviceTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if (hasScheduledA2dpDockTimeout() && mDockAddress != null) { - cancelA2dpDeviceTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress, - AudioSystem.AUDIO_FORMAT_DEFAULT); - } - } - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - "onSetA2dpSinkConnectionState"); - setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); - } - makeA2dpDeviceAvailable(address, btDevice.getName(), - "onSetA2dpSinkConnectionState", a2dpCodec); - } - } - } - - private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - - if (DEBUG_VOL) { - Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcAvailable(address); - } - } - } - - private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state) - { - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceAvailable(address, btDevice.getName(), - "onSetHearingAidConnectionState"); - } - } - } - - private void setCurrentAudioRouteNameIfPossible(String name) { - synchronized (mCurAudioRoutes) { - if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { - if (name != null || !isCurrentDeviceConnected()) { - mCurAudioRoutes.bluetoothName = name; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private boolean isCurrentDeviceConnected() { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) { - return true; - } - } - return false; - } - - private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpDeviceConfigChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - // Device is connected - if (deviceSpec.mDeviceCodecFormat != a2dpCodec) { - deviceSpec.mDeviceCodecFormat = a2dpCodec; - mConnectedDevices.replace(key, deviceSpec); - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" - + deviceSpec.mDeviceCodecFormat); - } - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), deviceSpec.mDeviceCodecFormat) - != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - - /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */ - public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpActiveDeviceChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - - // Device is connected - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, device, - "onBluetoothA2dpActiveDeviceChange"); - setDeviceVolume(streamState, device); - } - - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - public void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = support; - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); - } - } - - private boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) - + " address:" + address + " name:" + deviceName + ")"); - } - synchronized (mConnectedDevices) { - String deviceKey = makeDeviceListKey(device, address); - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceKey:" + deviceKey); - } - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - boolean isConnected = deviceSpec != null; - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected); - } - if (connect && !isConnected) { - final int res = AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - if (res != AudioSystem.AUDIO_STATUS_OK) { - Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + - " due to command error " + res ); - return false; - } - mConnectedDevices.put(deviceKey, new DeviceListSpec(device, - deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - device, 0, null, 0); - return true; - } else if (!connect && isConnected) { - AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - // always remove even if disconnection failed - mConnectedDevices.remove(deviceKey); - return true; - } - Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec=" - + deviceSpec + ", connect=" + connect); - } - return false; - } - - // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only - // sent if: - // - none of these devices are connected anymore after one is disconnected AND - // - the device being disconnected is actually used for music. - // Access synchronized on mConnectedDevices - int mBecomingNoisyIntentDevices = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI | - AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | - AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_HEARING_AID; - - // must be called before removing the device from mConnectedDevices - // Called synchronized on mConnectedDevices - // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying - // from AudioSystem - private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { - int delay = 0; - if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) { - int devices = 0; - for (int i = 0; i < mConnectedDevices.size(); i++) { - int dev = mConnectedDevices.valueAt(i).mDeviceType; - if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) - && ((dev & mBecomingNoisyIntentDevices) != 0)) { - devices |= dev; - } - } - if (musicDevice == AudioSystem.DEVICE_NONE) { - musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - } - // ignore condition on device being actually used for music when in communication - // because music routing is altered in this case. - // also checks whether media routing if affected by a dynamic policy - if (((device == musicDevice) || isInCommunication()) && (device == devices) - && !hasMediaDynamicPolicy()) { - mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY); - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); - delay = 1000; - } - } - - return delay; } /** * @return true if there is currently a registered dynamic mixing policy that affects media */ - private boolean hasMediaDynamicPolicy() { + /*package*/ boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; @@ -6578,213 +5028,25 @@ public class AudioService extends IAudioService.Stub } } - private void updateAudioRoutes(int device, int state) - { - int connType = 0; - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - connType = AudioRoutesInfo.MAIN_HEADSET; - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - connType = AudioRoutesInfo.MAIN_HEADPHONES; - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - connType = AudioRoutesInfo.MAIN_HDMI; - } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE|| - device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - connType = AudioRoutesInfo.MAIN_USB; - } - - synchronized (mCurAudioRoutes) { - if (connType != 0) { - int newConn = mCurAudioRoutes.mainType; - if (state != 0) { - newConn |= connType; - } else { - newConn &= ~connType; - } - if (newConn != mCurAudioRoutes.mainType) { - mCurAudioRoutes.mainType = newConn; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private void sendDeviceConnectionIntent(int device, int state, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + - " state:0x" + Integer.toHexString(state) + " address:" + address + - " name:" + deviceName + ");"); - } - Intent intent = new Intent(); - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 0); - } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", - AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); - } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) { - if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else { - // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing - return; - } - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - configureHdmiPlugIntent(intent, state); - } - - if (intent.getAction() == null) { - return; - } - - intent.putExtra(CONNECT_INTENT_KEY_STATE, state); - intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); - intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); - - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - - final long ident = Binder.clearCallingIdentity(); - try { - ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_ALL_USB; - - private void onSetWiredDeviceConnectionState(int device, int state, String address, - String deviceName, String caller) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) - + " state:" + Integer.toHexString(state) - + " address:" + address - + " deviceName:" + deviceName - + " caller: " + caller + ");"); - } - - synchronized (mConnectedDevices) { - if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { - setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0"); - } - - if (!handleDeviceConnection(state == 1, device, address, deviceName)) { - // change of connection state failed, bailout - return; - } - if (state != 0) { - if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { - setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0"); - } - if ((device & mSafeMediaVolumeDevices) != 0) { - sendMsg(mAudioHandler, - MSG_CHECK_MUSIC_ACTIVE, - SENDMSG_REPLACE, - 0, - 0, - caller, - MUSIC_ACTIVE_POLL_PERIOD_MS); - } - // Television devices without CEC service apply software volume on HDMI output - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; - checkAllFixedVolumeDevices(); - synchronized (mHdmiClientLock) { - if (mHdmiManager != null && mHdmiPlaybackClient != null) { - mHdmiCecSink = false; - mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); - } - } - } - if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) { - sendEnabledSurroundFormats(mContentResolver, true); - } - } else { - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - synchronized (mHdmiClientLock) { - if (mHdmiManager != null) { - mHdmiCecSink = false; - } - } - } - } - sendDeviceConnectionIntent(device, state, address, deviceName); - updateAudioRoutes(device, state); - } - } - - private void configureHdmiPlugIntent(Intent intent, int state) { - intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); - intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); - if (state == 1) { - ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); - int[] portGeneration = new int[1]; - int status = AudioSystem.listAudioPorts(ports, portGeneration); - if (status == AudioManager.SUCCESS) { - for (AudioPort port : ports) { - if (port instanceof AudioDevicePort) { - final AudioDevicePort devicePort = (AudioDevicePort) port; - if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI || - devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) { - // format the list of supported encodings - int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); - if (formats.length > 0) { - ArrayList<Integer> encodingList = new ArrayList(1); - for (int format : formats) { - // a format in the list can be 0, skip it - if (format != AudioFormat.ENCODING_INVALID) { - encodingList.add(format); - } - } - int[] encodingArray = new int[encodingList.size()]; - for (int i = 0 ; i < encodingArray.length ; i++) { - encodingArray[i] = encodingList.get(i); - } - intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); - } - // find the maximum supported number of channels - int maxChannels = 0; - for (int mask : devicePort.channelMasks()) { - int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); - if (channelCount > maxChannels) { - maxChannels = channelCount; - } - } - intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); - } - } - } - } + /*package*/ void checkMusicActive(int deviceType, String caller) { + if ((deviceType & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); } } - /* cache of the address of the last dock the device was connected to */ - private String mDockAddress; - /** * Receiver for misc intent broadcasts the Phone app cares about. */ private class AudioServiceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + final String action = intent.getAction(); int outDevice; int inDevice; int state; @@ -6812,75 +5074,16 @@ public class AudioService extends IAudioService.Stub } // Low end docks have a menu to enable or disable audio // (see mDockAudioMediaEnabled) - if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) && - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config, - "ACTION_DOCK_EVENT intent")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); + if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) + || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) + && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config, + "ACTION_DOCK_EVENT intent"); } mDockState = dockState; - } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { - BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - setBtScoActiveDevice(btDevice); - } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { - boolean broadcast = false; - int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - synchronized (mScoClients) { - int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - // broadcast intent if the connection was initated by AudioService - if (!mScoClients.isEmpty() && - (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || - mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATING)) { - broadcast = true; - } - switch (btState) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - setBluetoothScoOn(true); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - setBluetoothScoOn(false); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // startBluetoothSco called after stopBluetoothSco - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcast = false; - break; - } - } - // Tear down SCO if disconnected from external - clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - default: - // do not broadcast CONNECTING or invalid state - broadcast = false; - break; - } - } - if (broadcast) { - broadcastScoConnectionState(scoAudioState); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); - sendStickyBroadcastToAll(newIntent); - } + } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED) + || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + mDeviceBroker.receiveBtEvent(intent); } else if (action.equals(Intent.ACTION_SCREEN_ON)) { if (mMonitorRotation) { RotationHelper.enable(); @@ -6898,13 +5101,7 @@ public class AudioService extends IAudioService.Stub if (mUserSwitchedReceived) { // attempt to stop music playback for background user except on first user // switch (i.e. first boot) - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); + mDeviceBroker.broadcastBecomingNoisy(); } mUserSwitchedReceived = true; // the current audio focus owner is no longer valid @@ -6938,7 +5135,7 @@ public class AudioService extends IAudioService.Stub state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) { - disconnectAllBluetoothProfiles(); + mDeviceBroker.disconnectAllBluetoothProfiles(); } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { @@ -7178,22 +5375,17 @@ public class AudioService extends IAudioService.Stub // take new state into account for streams muted by ringer mode setRingerModeInt(getRingerModeInternal(), false); } - - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_SYSTEM, + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, - new String("handleConfigurationChanged"), - 0); - + "handleConfigurationChanged"); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); + } } mVolumeController.setLayoutDirection(config.getLayoutDirection()); @@ -7202,28 +5394,6 @@ public class AudioService extends IAudioService.Stub } } - // Handles request to override default use of A2DP for media. - // Must be called synchronized on mConnectedDevices - public void setBluetoothA2dpOnInt(boolean on, String eventSource) { - synchronized (mBluetoothA2dpEnabledLock) { - mBluetoothA2dpEnabled = on; - mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); - setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource); - } - } - - // Must be called synchronized on mConnectedDevices - private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) { - if (usage == AudioSystem.FOR_MEDIA) { - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource)); - AudioSystem.setForceUse(usage, config); - } - @Override public void setRingtonePlayer(IRingtonePlayer player) { mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null); @@ -7237,11 +5407,7 @@ public class AudioService extends IAudioService.Stub @Override public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { - synchronized (mCurAudioRoutes) { - AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); - mRoutesObservers.register(observer); - return routes; - } + return mDeviceBroker.startWatchingRoutes(observer); } @@ -7283,9 +5449,9 @@ public class AudioService extends IAudioService.Stub // the headset is compliant to EN 60950 with a max loudness of 100dB SPL. private int mSafeUsbMediaVolumeIndex; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, - private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | - AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_USB_HEADSET; + /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET + | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_USB_HEADSET; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. @@ -7438,9 +5604,8 @@ public class AudioService extends IAudioService.Stub mHdmiSystemAudioSupported = on; final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, - config, "setHdmiSystemAudioSupported")); - AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config, + "setHdmiSystemAudioSupported"); } device = getDevicesForStream(AudioSystem.STREAM_MUSIC); } @@ -7553,14 +5718,14 @@ public class AudioService extends IAudioService.Stub // logs for wired + A2DP device connections: // - wired: logged before onSetWiredDeviceConnectionState() is executed // - A2DP: logged at reception of method call - final private AudioEventLogger mDeviceLogger = new AudioEventLogger( - LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection"); + /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger( + LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI"); - final private AudioEventLogger mForceUseLogger = new AudioEventLogger( + static final AudioEventLogger sForceUseLogger = new AudioEventLogger( LOG_NB_EVENTS_FORCE_USE, "force use (logged before setForceUse() is executed)"); - final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, + static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, "volume changes (logged when command received by AudioService)"); final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY, @@ -7613,8 +5778,9 @@ public class AudioService extends IAudioService.Stub dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); - pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType)); - pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName); + pw.print(" mMainType=0x"); pw.println(Integer.toHexString( + mDeviceBroker.getCurAudioRoutes().mainType)); + pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName); pw.println("\nOther state:"); pw.print(" mVolumeController="); pw.println(mVolumeController); @@ -7630,7 +5796,8 @@ public class AudioService extends IAudioService.Stub pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); - pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); + pw.print(" mAvrcpAbsVolSupported="); + pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported()); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -7643,11 +5810,11 @@ public class AudioService extends IAudioService.Stub pw.println("\nEvent logs:"); mModeLogger.dump(pw); pw.println("\n"); - mDeviceLogger.dump(pw); + sDeviceLogger.dump(pw); pw.println("\n"); - mForceUseLogger.dump(pw); + sForceUseLogger.dump(pw); pw.println("\n"); - mVolumeLogger.dump(pw); + sVolumeLogger.dump(pw); } private static String safeMediaVolumeStateToString(int state) { @@ -8270,6 +6437,11 @@ public class AudioService extends IAudioService.Stub } //====================== + // Audio device management + //====================== + private final AudioDeviceBroker mDeviceBroker; + + //====================== // Audio policy proxy //====================== private static final class AudioDeviceArray { diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 9d9e35bdf2b2..7ccb45e97912 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -19,7 +19,7 @@ package com.android.server.audio; import android.media.AudioManager; import android.media.AudioSystem; -import com.android.server.audio.AudioService.WiredDeviceConnectionState; +import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState; public class AudioServiceEvents { @@ -89,9 +89,11 @@ public class AudioServiceEvents { } final static class VolumeEvent extends AudioEventLogger.Event { - final static int VOL_ADJUST_SUGG_VOL = 0; - final static int VOL_ADJUST_STREAM_VOL = 1; - final static int VOL_SET_STREAM_VOL = 2; + static final int VOL_ADJUST_SUGG_VOL = 0; + static final int VOL_ADJUST_STREAM_VOL = 1; + static final int VOL_SET_STREAM_VOL = 2; + static final int VOL_SET_HEARING_AID_VOL = 3; + static final int VOL_SET_AVRCP_VOL = 4; final int mOp; final int mStream; @@ -107,6 +109,24 @@ public class AudioServiceEvents { mCaller = caller; } + VolumeEvent(int op, int index, int gainDb) { + mOp = op; + mVal1 = index; + mVal2 = gainDb; + //unused + mStream = -1; + mCaller = null; + } + + VolumeEvent(int op, int index) { + mOp = op; + mVal1 = index; + //unused + mVal2 = 0; + mStream = -1; + mCaller = null; + } + @Override public String eventToString() { switch (mOp) { @@ -131,6 +151,15 @@ public class AudioServiceEvents { .append(" flags:0x").append(Integer.toHexString(mVal2)) .append(") from ").append(mCaller) .toString(); + case VOL_SET_HEARING_AID_VOL: + return new StringBuilder("setHearingAidVolume:") + .append(" index:").append(mVal1) + .append(" gain dB:").append(mVal2) + .toString(); + case VOL_SET_AVRCP_VOL: + return new StringBuilder("setAvrcpVolume:") + .append(" index:").append(mVal1) + .toString(); default: return new StringBuilder("FIXME invalid op:").append(mOp).toString(); } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java new file mode 100644 index 000000000000..bf325013c7da --- /dev/null +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -0,0 +1,949 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * @hide + * Class to encapsulate all communication with Bluetooth services + */ +public class BtHelper { + + private static final String TAG = "AS.BtHelper"; + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + BtHelper(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // List of clients having issued a SCO start request + private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>(); + + // BluetoothHeadset API to control SCO connection + private BluetoothHeadset mBluetoothHeadset; + + // Bluetooth headset device + private BluetoothDevice mBluetoothHeadsetDevice; + + // Indicate if SCO audio connection is currently active and if the initiator is + // audio service (internal) or bluetooth headset (external) + private int mScoAudioState; + // SCO audio state is not active + private static final int SCO_STATE_INACTIVE = 0; + // SCO audio activation request waiting for headset service to connect + private static final int SCO_STATE_ACTIVATE_REQ = 1; + // SCO audio state is active or starting due to a request from AudioManager API + private static final int SCO_STATE_ACTIVE_INTERNAL = 3; + // SCO audio deactivation request waiting for headset service to connect + private static final int SCO_STATE_DEACTIVATE_REQ = 4; + // SCO audio deactivation in progress, waiting for Bluetooth audio intent + private static final int SCO_STATE_DEACTIVATING = 5; + + // SCO audio state is active due to an action in BT handsfree (either voice recognition or + // in call audio) + private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; + + // Indicates the mode used for SCO audio connection. The mode is virtual call if the request + // originated from an app targeting an API version before JB MR2 and raw audio after that. + private int mScoAudioMode; + // SCO audio mode is undefined + /*package*/ static final int SCO_MODE_UNDEFINED = -1; + // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) + /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; + // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) + private static final int SCO_MODE_RAW = 1; + // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) + private static final int SCO_MODE_VR = 2; + + private static final int SCO_MODE_MAX = 2; + + // Current connection state indicated by bluetooth headset + private int mScoConnectionState; + + private static final int BT_HEARING_AID_GAIN_MIN = -128; + + @GuardedBy("mDeviceBroker.mHearingAidLock") + private BluetoothHearingAid mHearingAid; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private BluetoothA2dp mA2dp; + // If absolute volume is supported in AVRCP device + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private boolean mAvrcpAbsVolSupported = false; + + //---------------------------------------------------------------------- + /*package*/ static class BluetoothA2dpDeviceInfo { + private final @NonNull BluetoothDevice mBtDevice; + private final int mVolume; + private final int mCodec; + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { + this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); + } + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { + mBtDevice = btDevice; + mVolume = volume; + mCodec = codec; + } + + public @NonNull BluetoothDevice getBtDevice() { + return mBtDevice; + } + + public int getVolume() { + return mVolume; + } + + public int getCodec() { + return mCodec; + } + } + + //---------------------------------------------------------------------- + // Interface for AudioDeviceBroker + + /*package*/ void onSystemReady() { + mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; + resetBluetoothSco(); + getBluetoothHeadset(); + + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + sendStickyBroadcastToAll(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.A2DP); + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID); + } + } + + @GuardedBy("mBluetoothA2dpEnabledLock") + /*package*/ void onAudioServerDiedRestoreA2dp() { + final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + return (mA2dp != null && mAvrcpAbsVolSupported); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + mAvrcpAbsVolSupported = supported; + } + + /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + if (mA2dp == null) { + if (AudioService.DEBUG_VOL) { + Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); + return; + } + } + if (!mAvrcpAbsVolSupported) { + return; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10)); + mA2dp.setAvrcpAbsoluteVolume(index / 10); + } + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + if (mA2dp == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + if (btCodecStatus == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); + if (btCodecConfig == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); + } + + /*package*/ void receiveBtEvent(Intent intent) { + final String action = intent.getAction(); + if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { + BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + setBtScoActiveDevice(btDevice); + } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + boolean broadcast = false; + int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; + synchronized (mScoClients) { + int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + // broadcast intent if the connection was initated by AudioService + if (!mScoClients.isEmpty() + && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL + || mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATING)) { + broadcast = true; + } + switch (btState) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // startBluetoothSco called after stopBluetoothSco + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcast = false; + break; + } + } + // Tear down SCO if disconnected from external + clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; + } + } + if (broadcast) { + broadcastScoConnectionState(scoAudioState); + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); + sendStickyBroadcastToAll(newIntent); + } + } + } + + /** + * + * @return false if SCO isn't connected + */ + /*package*/ boolean isBluetoothScoOn() { + synchronized (mScoClients) { + if ((mBluetoothHeadset != null) + && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { + Log.w(TAG, "isBluetoothScoOn(true) returning false because " + + mBluetoothHeadsetDevice + " is not in audio connected mode"); + return false; + } + } + return true; + } + + /** + * Disconnect all SCO connections started by {@link AudioManager} except those started by + * {@param exceptPid} + * + * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept + */ + /*package*/ void disconnectBluetoothSco(int exceptPid) { + synchronized (mScoClients) { + checkScoAudioState(); + if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { + return; + } + clearAllScoClients(exceptPid, true); + } + } + + /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + ScoClient client = getScoClient(cb, true); + // The calling identity must be cleared before calling ScoClient.incCount(). + // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + // The caller identity must be cleared after getScoClient() because it is needed if a new + // client is created. + final long ident = Binder.clearCallingIdentity(); + try { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.incCount(scoAudioMode); + } catch (NullPointerException e) { + Log.e(TAG, "Null ScoClient", e); + } + Binder.restoreCallingIdentity(ident); + } + + /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) { + ScoClient client = getScoClient(cb, false); + // The calling identity must be cleared before calling ScoClient.decCount(). + // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + final long ident = Binder.clearCallingIdentity(); + if (client != null) { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.decCount(); + } + Binder.restoreCallingIdentity(ident); + } + + + /*package*/ void setHearingAidVolume(int index, int streamType) { + synchronized (mDeviceBroker.mHearingAidLock) { + if (mHearingAid == null) { + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: null mHearingAid"); + } + return; + } + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, + AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) { + gainDB = BT_HEARING_AID_GAIN_MIN; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" + + index + " gain=" + gainDB); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); + mHearingAid.setVolume(gainDB); + } + } + + //---------------------------------------------------------------------- + private void broadcastScoConnectionState(int state) { + mDeviceBroker.broadcastScoConnectionState(state); + } + + /*package*/ void onBroadcastScoConnectionState(int state) { + if (state == mScoConnectionState) { + return; + } + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, + mScoConnectionState); + sendStickyBroadcastToAll(newIntent); + mScoConnectionState = state; + } + + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { + if (btDevice == null) { + return true; + } + String address = btDevice.getAddress(); + BluetoothClass btClass = btDevice.getBluetoothClass(); + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + int[] outDeviceTypes = { + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT + }; + if (btClass != null) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; + break; + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; + break; + } + } + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + String btDeviceName = btDevice.getName(); + boolean result = false; + if (isActive) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceTypes[0], address, btDeviceName); + } else { + for (int outDeviceType : outDeviceTypes) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceType, address, btDeviceName); + } + } + // handleDeviceConnection() && result to make sure the method get executed + result = mDeviceBroker.handleDeviceConnection( + isActive, inDevice, address, btDeviceName) && result; + return result; + } + + private void setBtScoActiveDevice(BluetoothDevice btDevice) { + synchronized (mScoClients) { + Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); + final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; + if (Objects.equals(btDevice, previousActiveDevice)) { + return; + } + if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { + Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " + + previousActiveDevice); + } + if (!handleBtScoActiveDeviceChange(btDevice, true)) { + Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); + // set mBluetoothHeadsetDevice to null when failing to add new device + btDevice = null; + } + mBluetoothHeadsetDevice = btDevice; + if (mBluetoothHeadsetDevice == null) { + resetBluetoothSco(); + } + } + } + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + final BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + if (btDevice == null) { + Log.e(TAG, "Invalid null device in BT profile listener"); + return; + } + final @BluetoothProfile.BtProfileState int state = + mA2dp.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSinkConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + } + break; + + case BluetoothProfile.A2DP_SINK: + deviceList = proxy.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + proxy.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSourceConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); + mBluetoothHeadset = (BluetoothHeadset) proxy; + setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + status = connectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } + break; + case SCO_STATE_DEACTIVATE_REQ: + status = disconnectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } + break; + } + } + if (!status) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + } + break; + + case BluetoothProfile.HEARING_AID: + mHearingAid = (BluetoothHearingAid) proxy; + deviceList = mHearingAid.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + mHearingAid.getConnectionState(btDevice); + mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + btDevice, state, + /*suppressNoisyIntent*/ false, + /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, + /*eventSource*/ "mBluetoothProfileServiceListener"); + } + break; + + default: + break; + } + } + public void onServiceDisconnected(int profile) { + + switch (profile) { + case BluetoothProfile.A2DP: + mDeviceBroker.handleDisconnectA2dp(); + break; + + case BluetoothProfile.A2DP_SINK: + mDeviceBroker.handleDisconnectA2dpSink(); + break; + + case BluetoothProfile.HEADSET: + disconnectHeadset(); + break; + + case BluetoothProfile.HEARING_AID: + mDeviceBroker.handleDisconnectHearingAid(); + break; + + default: + break; + } + } + }; + + void disconnectAllBluetoothProfiles() { + mDeviceBroker.handleDisconnectA2dp(); + mDeviceBroker.handleDisconnectA2dpSink(); + disconnectHeadset(); + mDeviceBroker.handleDisconnectHearingAid(); + } + + private void disconnectHeadset() { + synchronized (mScoClients) { + setBtScoActiveDevice(null); + mBluetoothHeadset = null; + } + } + + //---------------------------------------------------------------------- + private class ScoClient implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private int mCreatorPid; + private int mStartcount; // number of SCO connections started by this client + + ScoClient(IBinder cb) { + mCb = cb; + mCreatorPid = Binder.getCallingPid(); + mStartcount = 0; + } + + public void binderDied() { + synchronized (mScoClients) { + Log.w(TAG, "SCO client died"); + int index = mScoClients.indexOf(this); + if (index < 0) { + Log.w(TAG, "unregistered SCO client died"); + } else { + clearCount(true); + mScoClients.remove(this); + } + } + } + + public void incCount(int scoAudioMode) { + synchronized (mScoClients) { + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + if (mStartcount == 0) { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "ScoClient incCount() could not link to " + + mCb + " binder death"); + } + } + mStartcount++; + } + } + + public void decCount() { + synchronized (mScoClients) { + if (mStartcount == 0) { + Log.w(TAG, "ScoClient.decCount() already 0"); + } else { + mStartcount--; + if (mStartcount == 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "decCount() going to 0 but not registered to binder"); + } + } + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public void clearCount(boolean stopSco) { + synchronized (mScoClients) { + if (mStartcount != 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "clearCount() mStartcount: " + + mStartcount + " != 0 but not registered to binder"); + } + } + mStartcount = 0; + if (stopSco) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public int getCount() { + return mStartcount; + } + + public IBinder getBinder() { + return mCb; + } + + public int getPid() { + return mCreatorPid; + } + + public int totalCount() { + synchronized (mScoClients) { + int count = 0; + for (ScoClient mScoClient : mScoClients) { + count += mScoClient.getCount(); + } + return count; + } + } + + private void requestScoState(int state, int scoAudioMode) { + checkScoAudioState(); + int clientCount = totalCount(); + if (clientCount != 0) { + Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode + + ", clientCount=" + clientCount); + return; + } + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + // Accept SCO audio activation only in NORMAL audio mode or if the mode is + // currently controlled by the same client process. + synchronized (mDeviceBroker.mSetModeLock) { + int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty() + ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid(); + if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { + Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " + + modeOwnerPid + " != creatorPid " + mCreatorPid); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return; + } + switch (mScoAudioState) { + case SCO_STATE_INACTIVE: + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = Settings.Global.getInt( + mDeviceBroker.getContentResolver(), + "bluetooth_sco_channel_" + + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + } + } + } + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " connection, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + Log.w(TAG, "requestScoState: no active device while connecting," + + " mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } else { + Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice + + " failed, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_DEACTIVATING: + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + break; + case SCO_STATE_DEACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to connect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + + } + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVE_INTERNAL: + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " disconnection, mScoAudioMode=" + mScoAudioMode); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to disconnect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + } + } + } + + //----------------------------------------------------- + // Utilities + private void sendStickyBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.disconnectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.stopVoiceRecognition(device); + default: + return false; + } + } + + private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.connectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.startScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.startVoiceRecognition(device); + default: + return false; + } + } + + /*package*/ void resetBluetoothSco() { + synchronized (mScoClients) { + clearAllScoClients(0, false); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + AudioSystem.setParameters("A2dpSuspended=false"); + mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); + } + + + private void checkScoAudioState() { + synchronized (mScoClients) { + if (mBluetoothHeadset != null + && mBluetoothHeadsetDevice != null + && mScoAudioState == SCO_STATE_INACTIVE + && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + } + } + + + private ScoClient getScoClient(IBinder cb, boolean create) { + synchronized (mScoClients) { + for (ScoClient existingClient : mScoClients) { + if (existingClient.getBinder() == cb) { + return existingClient; + } + } + if (create) { + ScoClient newClient = new ScoClient(cb); + mScoClients.add(newClient); + return newClient; + } + return null; + } + } + + private void clearAllScoClients(int exceptPid, boolean stopSco) { + synchronized (mScoClients) { + ScoClient savedClient = null; + for (ScoClient cl : mScoClients) { + if (cl.getPid() != exceptPid) { + cl.clearCount(stopSco); + } else { + savedClient = cl; + } + } + mScoClients.clear(); + if (savedClient != null) { + mScoClients.add(savedClient); + } + } + } + + private boolean getBluetoothHeadset() { + boolean result = false; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + result = adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); + } + // If we could not get a bluetooth headset proxy, send a failure message + // without delay to reset the SCO audio state and clear SCO clients. + // If we could get a proxy, send a delayed failure message that will reset our state + // in case we don't receive onServiceConnected(). + mDeviceBroker.handleFailureToConnectToBtHeadsetService( + result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0); + return result; + } + + private int mapBluetoothCodecToAudioFormat(int btCodecType) { + switch (btCodecType) { + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: + return AudioSystem.AUDIO_FORMAT_SBC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: + return AudioSystem.AUDIO_FORMAT_AAC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: + return AudioSystem.AUDIO_FORMAT_APTX; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: + return AudioSystem.AUDIO_FORMAT_APTX_HD; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: + return AudioSystem.AUDIO_FORMAT_LDAC; + default: + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + } +} diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index dc564ba69e0d..3a25d980e97a 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -202,7 +202,7 @@ public final class PlaybackActivityMonitor if (mPrivilegedAlarmActiveCount++ == 0) { mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER); - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED && @@ -211,7 +211,7 @@ public final class PlaybackActivityMonitor if (AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) == mMaxAlarmVolume) { - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 41fedc5fab45..15d66e6e646f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -29,10 +29,12 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.IActivityTaskManager; +import android.app.KeyguardManager; import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; @@ -377,6 +379,13 @@ public class BiometricService extends SystemService { new BiometricTaskStackListener(); private final Random mRandom = new Random(); + // TODO(b/123378871): Remove when moved. + // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app) + // receiver. BiometricService internally launches CDCA which invokes BiometricService to + // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl + // method to poke BiometricService - the result will then be forwarded to this receiver. + private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; + // The current authentication session, null if idle/done. We need to track both the current // and pending sessions since errors may be sent to either. private AuthSession mCurrentAuthSession; @@ -705,6 +714,22 @@ public class BiometricService extends SystemService { } } + // Launch CDC instead if necessary. CDC will return results through an AIDL call, since + // we can't get activity results. Store the receiver somewhere so we can forward the + // result back to the client. + // TODO(b/123378871): Remove when moved. + if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) { + mConfirmDeviceCredentialReceiver = receiver; + final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class); + // Use this so we don't need to duplicate logic.. + final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */, + null /* description */); + // Then give it the bundle to do magic behavior.. + intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle); + getContext().startActivityAsUser(intent, UserHandle.CURRENT); + return; + } + mHandler.post(() -> { final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); final int modality = result.first; @@ -745,6 +770,36 @@ public class BiometricService extends SystemService { }); } + @Override // Binder call + public void onConfirmDeviceCredentialSuccess() { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCASuccess null!"); + return; + } + try { + mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + + @Override // Binder call + public void onConfirmDeviceCredentialError(int error, String message) { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + return; + } + try { + mConfirmDeviceCredentialReceiver.onError(error, message); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + /** * authenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 3db6a74a1c6c..fcf821f23ae9 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -48,14 +48,12 @@ import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; -import android.util.StatsLog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; -import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; @@ -588,11 +586,6 @@ public class FingerprintService extends BiometricServiceBase { public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) { mHandler.post(() -> { FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode); - if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE - && getCurrentClient() instanceof AuthenticationClient) { - // Ignore enrollment acquisitions or acquisitions when we are locked out. - StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto); - } }); } @@ -602,22 +595,6 @@ public class FingerprintService extends BiometricServiceBase { mHandler.post(() -> { Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId); FingerprintService.super.handleAuthenticated(fp, token); - // Send authentication to statsd. - final boolean authenticated = fingerId != 0; - StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto, - authenticated); - if (!authenticated) { - // If we failed to authenticate because of a lockout, inform statsd. - final int lockoutMode = getLockoutMode(); - if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) { - StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId, - mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT); - } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) { - StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId, - mIsCrypto, - StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT); - } - } }); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index cb3f91b7b6bb..b89768ac4b68 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.SensorManager; import android.hardware.display.AmbientBrightnessDayStats; @@ -93,9 +94,9 @@ import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.wm.SurfaceAnimationThread; import com.android.server.wm.WindowManagerInternal; -import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -294,6 +295,7 @@ public final class DisplayManagerService extends SystemService { // is rejected by the system. private final Curve mMinimumBrightnessCurve; private final Spline mMinimumBrightnessSpline; + private final ColorSpace mWideColorSpace; public DisplayManagerService(Context context) { this(context, new Injector()); @@ -322,6 +324,8 @@ public final class DisplayManagerService extends SystemService { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting(); mCurrentUserId = UserHandle.USER_SYSTEM; + ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces(); + mWideColorSpace = colorSpaces[1]; } public void setupSchedulerPolicies() { @@ -1073,6 +1077,10 @@ public final class DisplayManagerService extends SystemService { return mMinimumBrightnessCurve; } + int getPreferredWideGamutColorSpaceIdInternal() { + return mWideColorSpace.getId(); + } + private void setBrightnessConfigurationForUserInternal( @Nullable BrightnessConfiguration c, @UserIdInt int userId, @Nullable String packageName) { @@ -2128,6 +2136,16 @@ public final class DisplayManagerService extends SystemService { } } + @Override // Binder call + public int getPreferredWideGamutColorSpaceId() { + final long token = Binder.clearCallingIdentity(); + try { + return getPreferredWideGamutColorSpaceIdInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + void setBrightness(int brightness) { Settings.System.putIntForUser(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4db541c29448..99bed732446c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -207,6 +207,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_SET_ACTIVE = 3020; static final int MSG_SET_INTERACTIVE = 3030; static final int MSG_REPORT_FULLSCREEN_MODE = 3045; + static final int MSG_REPORT_PRE_RENDERED = 3060; + static final int MSG_APPLY_IME_VISIBILITY = 3070; static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; @@ -3327,6 +3329,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @BinderThread + private void reportPreRendered(IBinder token, EditorInfo info) { + synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } + if (mCurClient != null && mCurClient.client != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( + MSG_REPORT_PRE_RENDERED, info, mCurClient)); + } + } + } + + @BinderThread + private void applyImeVisibility(IBinder token, boolean setVisible) { + synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } + if (mCurClient != null && mCurClient.client != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( + MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient)); + } + } + } + private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -3580,6 +3608,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } + case MSG_REPORT_PRE_RENDERED: { + args = (SomeArgs) msg.obj; + final EditorInfo info = (EditorInfo) args.arg1; + final ClientState clientState = (ClientState) args.arg2; + try { + clientState.client.reportPreRendered(info); + } catch (RemoteException e) { + Slog.w(TAG, "Got RemoteException sending " + + "reportPreRendered(" + info + ") notification to pid=" + + clientState.pid + " uid=" + clientState.uid); + } + args.recycle(); + return true; + } + case MSG_APPLY_IME_VISIBILITY: { + final boolean setVisible = msg.arg1 != 0; + final ClientState clientState = (ClientState) msg.obj; + try { + clientState.client.applyImeVisibility(setVisible); + } catch (RemoteException e) { + Slog.w(TAG, "Got RemoteException sending " + + "applyImeVisibility(" + setVisible + ") notification to pid=" + + clientState.pid + " uid=" + clientState.uid); + } + return true; + } // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: @@ -4756,5 +4810,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void notifyUserAction() { mImms.notifyUserAction(mToken); } + + @BinderThread + @Override + public void reportPreRendered(EditorInfo info) { + mImms.reportPreRendered(mToken, info); + } + + @BinderThread + @Override + public void applyImeVisibility(boolean setVisible) { + mImms.applyImeVisibility(mToken, setVisible); + } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index cfc85dac8a53..4349b4aa3603 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -34,6 +34,7 @@ import android.os.UserManagerInternal; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; @@ -756,6 +757,14 @@ final class InputMethodUtils { */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); + private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { + Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); + } + + private static final UserManagerInternal sUserManagerInternal = + LocalServices.getService(UserManagerInternal.class); + private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; @@ -833,7 +842,9 @@ final class InputMethodUtils { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putStringForUser(mResolver, key, str, userId); } } @@ -852,14 +863,16 @@ final class InputMethodUtils { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putIntForUser(mResolver, key, value, userId); } } private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); - return result != null ? Integer.parseInt(result) : 0; + return result != null ? Integer.parseInt(result) : defaultValue; } return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); } diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 82bfa511507f..ed1a6d0beabf 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -33,6 +33,7 @@ import android.text.format.Time; import android.util.ArraySet; import android.util.Pair; import android.util.Slog; +import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -40,6 +41,7 @@ import com.android.server.LocalServices; import com.android.server.job.GrantedUriPermissions; import com.android.server.job.JobSchedulerInternal; import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobServerProtoEnums; import com.android.server.job.JobStatusDumpProto; import com.android.server.job.JobStatusShortInfoProto; @@ -80,6 +82,28 @@ public final class JobStatus { static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22; + /** + * The constraints that we want to log to statsd. + * + * Constraints that can be inferred from other atoms have been excluded to avoid logging too + * much information and to reduce redundancy: + * + * * CONSTRAINT_CHARGING can be inferred with PluggedStateChanged (Atom #32) + * * CONSTRAINT_BATTERY_NOT_LOW can be inferred with BatteryLevelChanged (Atom #30) + * * CONSTRAINT_CONNECTIVITY can be partially inferred with ConnectivityStateChanged + * (Atom #98) and BatterySaverModeStateChanged (Atom #20). + * * CONSTRAINT_DEVICE_NOT_DOZING can be mostly inferred with DeviceIdleModeStateChanged + * (Atom #21) + * * CONSTRAINT_BACKGROUND_NOT_RESTRICTED can be inferred with BatterySaverModeStateChanged + * (Atom #20) + */ + private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER + | CONSTRAINT_DEADLINE + | CONSTRAINT_IDLE + | CONSTRAINT_STORAGE_NOT_LOW + | CONSTRAINT_TIMING_DELAY + | CONSTRAINT_WITHIN_QUOTA; + // Soft override: ignore constraints like time that don't affect API availability public static final int OVERRIDE_SOFT = 1; // Full override: ignore all constraints including API-affecting like connectivity @@ -976,6 +1000,12 @@ public final class JobStatus { } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; + if ((STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) { + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED, + sourceUid, null, getBatteryName(), getProtoConstraint(constraint), + state ? StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED + : StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED); + } return true; } @@ -1228,37 +1258,70 @@ public final class JobStatus { } } + /** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */ + private int getProtoConstraint(int constraint) { + switch (constraint) { + case CONSTRAINT_BACKGROUND_NOT_RESTRICTED: + return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED; + case CONSTRAINT_BATTERY_NOT_LOW: + return JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW; + case CONSTRAINT_CHARGING: + return JobServerProtoEnums.CONSTRAINT_CHARGING; + case CONSTRAINT_CONNECTIVITY: + return JobServerProtoEnums.CONSTRAINT_CONNECTIVITY; + case CONSTRAINT_CONTENT_TRIGGER: + return JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER; + case CONSTRAINT_DEADLINE: + return JobServerProtoEnums.CONSTRAINT_DEADLINE; + case CONSTRAINT_DEVICE_NOT_DOZING: + return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING; + case CONSTRAINT_IDLE: + return JobServerProtoEnums.CONSTRAINT_IDLE; + case CONSTRAINT_STORAGE_NOT_LOW: + return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW; + case CONSTRAINT_TIMING_DELAY: + return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY; + case CONSTRAINT_WITHIN_QUOTA: + return JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA; + default: + return JobServerProtoEnums.CONSTRAINT_UNKNOWN; + } + } + /** Writes constraints to the given repeating proto field. */ void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) { if ((constraints & CONSTRAINT_CHARGING) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CHARGING); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CHARGING); } if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_BATTERY_NOT_LOW); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW); } if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_STORAGE_NOT_LOW); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW); } if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_TIMING_DELAY); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_TIMING_DELAY); } if ((constraints & CONSTRAINT_DEADLINE) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEADLINE); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEADLINE); } if ((constraints & CONSTRAINT_IDLE) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_IDLE); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_IDLE); } if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONNECTIVITY); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONNECTIVITY); } if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONTENT_TRIGGER); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER); } if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEVICE_NOT_DOZING); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING); } if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) { - proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_WITHIN_QUOTA); + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA); + } + if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { + proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED); } } diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java index f1de37188510..e6f0ed9d14b0 100644 --- a/services/core/java/com/android/server/location/GeocoderProxy.java +++ b/services/core/java/com/android/server/location/GeocoderProxy.java @@ -20,8 +20,6 @@ import android.content.Context; import android.location.Address; import android.location.GeocoderParams; import android.location.IGeocodeProvider; -import android.os.RemoteException; -import android.util.Log; import com.android.internal.os.BackgroundThread; import com.android.server.ServiceWatcher; @@ -68,35 +66,22 @@ public class GeocoderProxy { public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { - final String[] result = new String[]{"Service not Available"}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - try { - result[0] = provider.getFromLocation( - latitude, longitude, maxResults, params, addrs); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return result[0]; + return provider.getFromLocation(latitude, longitude, maxResults, params, addrs); + }, "Service not Available"); } public String getFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) { - final String[] result = new String[]{"Service not Available"}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - try { - result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude, - lowerLeftLongitude, upperRightLatitude, upperRightLongitude, - maxResults, params, addrs); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return result[0]; + return provider.getFromLocationName(locationName, lowerLeftLatitude, + lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, params, addrs); + }, "Service not Available"); } } diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index a6da8c5e7713..6b5b1bebd20f 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -127,20 +127,16 @@ public class LocationProviderProxy extends AbstractLocationProvider { return mServiceWatcher.start(); } - private void initializeService(IBinder binder) { + private void initializeService(IBinder binder) throws RemoteException { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - try { - service.setLocationProviderManager(mManager); + service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } + synchronized (mRequestLock) { + if (mRequest != null) { + service.setRequest(mRequest, mWorkSource); } - } catch (RemoteException e) { - Log.w(TAG, e); } } @@ -157,63 +153,44 @@ public class LocationProviderProxy extends AbstractLocationProvider { } mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.setRequest(request, source); - } catch (RemoteException e) { - Log.w(TAG, e); - } + service.setRequest(request, source); }); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(" service=" + mServiceWatcher); - mServiceWatcher.runOnBinder(binder -> { + mServiceWatcher.runOnBinderBlocking(binder -> { try { TransferPipe.dumpAsync(binder, fd, args); } catch (IOException | RemoteException e) { - pw.println(" failed to dump location provider: " + e); + pw.println(" failed to dump location provider"); } - }); + return null; + }, null); } @Override public int getStatus(Bundle extras) { - int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - status[0] = service.getStatus(extras); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return status[0]; + return service.getStatus(extras); + }, LocationProvider.TEMPORARILY_UNAVAILABLE); } @Override public long getStatusUpdateTime() { - long[] updateTime = new long[] {0L}; - mServiceWatcher.runOnBinder(binder -> { + return mServiceWatcher.runOnBinderBlocking(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - updateTime[0] = service.getStatusUpdateTime(); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - return updateTime[0]; + return service.getStatusUpdateTime(); + }, 0L); } @Override public void sendExtraCommand(String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.sendExtraCommand(command, extras); - } catch (RemoteException e) { - Log.w(TAG, e); - } + service.sendExtraCommand(command, extras); }); } } diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS index 92b4d5fea113..c2c95e6042de 100644 --- a/services/core/java/com/android/server/location/OWNERS +++ b/services/core/java/com/android/server/location/OWNERS @@ -1,6 +1,8 @@ +aadmal@google.com arthuri@google.com bduddie@google.com gomo@google.com sooniln@google.com weiwa@google.com wyattriley@google.com +yuhany@google.com diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a1646862de9f..de3f50ad8c2c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1087,7 +1087,8 @@ public class NotificationManagerService extends SystemService { || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE) || action.equals(Intent.ACTION_PACKAGES_SUSPENDED) - || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) { + || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED) + || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); String pkgList[] = null; @@ -1108,6 +1109,23 @@ public class NotificationManagerService extends SystemService { uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); cancelNotifications = false; unhideNotifications = true; + } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { + final int distractionRestrictions = + intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS, + PackageManager.RESTRICTION_NONE); + if ((distractionRestrictions + & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + hideNotifications = true; + } else { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + unhideNotifications = true; + } + } else if (queryRestart) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)}; @@ -1651,6 +1669,7 @@ public class NotificationManagerService extends SystemService { IntentFilter suspendedPkgFilter = new IntentFilter(); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); + suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, suspendedPkgFilter, null, null); @@ -7743,6 +7762,20 @@ public class NotificationManagerService extends SystemService { mPackageIntentReceiver.onReceive(getContext(), intent); } + @VisibleForTesting + protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { + // only use for testing: mimic receive broadcast that package is (un)distracting + // but does not actually register that info with packagemanager + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); + + final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + /** * Wrapper for a StatusBarNotification object that allows transfer across a oneway * binder without sending large amounts of data over a oneway transaction. diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index ac965faedd34..ab49ebb66fbf 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -667,15 +667,15 @@ public final class NotificationRecord { getUserSentiment())); } } - if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) { + if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) { setSystemGeneratedSmartActions( - signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS)); + signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS)); MetricsLogger.action(getAdjustmentLogMaker() .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS, getSystemGeneratedSmartActions().size())); } - if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) { - setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES)); + if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) { + setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES)); MetricsLogger.action(getAdjustmentLogMaker() .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_REPLIES, getSmartReplies().size())); diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 3d88f20f0710..2aaa1edcfad9 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -176,6 +176,14 @@ public class NotificationShellCmd extends ShellCommand { // only use for testing mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired()); } + case "distract_package": { + // only use for testing + // Flag values are in + // {@link android.content.pm.PackageManager.DistractionRestriction}. + mDirectService.simulatePackageDistractionBroadcast( + Integer.parseInt(getNextArgRequired()), + getNextArgRequired().split(",")); + } break; case "post": case "notify": diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index eab5c8f866a8..146a2f3d8433 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -28,8 +28,10 @@ import android.app.NotificationManager; import android.app.PackageDeleteObserver; import android.app.PackageInstallObserver; import android.app.admin.DevicePolicyManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ApplicationInfo; @@ -138,6 +140,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private final Callbacks mCallbacks; + private volatile boolean mBootCompleted = false; + /** * File storing persisted {@link #mSessions} metadata. */ @@ -203,9 +207,20 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager = new StagingManager(pm); } + private void setBootCompleted() { + mBootCompleted = true; + } + public void systemReady() { mAppOps = mContext.getSystemService(AppOpsManager.class); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + setBootCompleted(); + mContext.unregisterReceiver(this); + } + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); synchronized (mSessions) { readSessionsLocked(); @@ -1126,8 +1141,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void onStagedSessionChanged(PackageInstallerSession session) { writeSessionsAsync(); - // TODO(b/118865310): don't send broadcast if system is not ready. - mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); + if (mBootCompleted) { + mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), + session.userId); + } } public void onSessionFinished(final PackageInstallerSession session, boolean success) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 692c032b1f70..6f1eeeb7de7a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -39,6 +39,7 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; @@ -258,6 +259,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHarmfulAppWarning(); case "get-harmful-app-warning": return runGetHarmfulAppWarning(); + case "get-stagedsessions": + return getStagedSessions(); case "uninstall-system-updates": return uninstallSystemUpdates(); default: { @@ -282,6 +285,28 @@ class PackageManagerShellCommand extends ShellCommand { return -1; } + private int getStagedSessions() { + final PrintWriter pw = getOutPrintWriter(); + try { + List<SessionInfo> stagedSessionsList = + mInterface.getPackageInstaller().getStagedSessions().getList(); + for (SessionInfo session: stagedSessionsList) { + pw.println("appPackageName = " + session.getAppPackageName() + + "; sessionId = " + session.getSessionId() + + "; isStaged = " + session.isStaged() + + "; isSessionReady = " + session.isSessionReady() + + "; isSessionApplied = " + session.isSessionApplied() + + "; isSessionFailed = " + session.isSessionFailed() + ";"); + } + } catch (RemoteException e) { + pw.println("Failure [" + + e.getClass().getName() + " - " + + e.getMessage() + "]"); + return 0; + } + return 1; + } + private int uninstallSystemUpdates() { final PrintWriter pw = getOutPrintWriter(); List<String> failedUninstalls = new LinkedList<>(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 30b5e49bc3fd..3c89d7801852 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1049,6 +1049,8 @@ public class PermissionManagerService { updatedUserIds); updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions, permissionsState, pkg, updatedUserIds); + + setAppOpsLocked(permissionsState, pkg); } // Persist the runtime permissions state for users with changes. If permissions @@ -1387,6 +1389,60 @@ public class PermissionManagerService { return updatedUserIds; } + /** + * Fix app-op modes for runtime permissions. + * + * @param permsState The state of the permissions of the package + * @param pkg The package information + */ + private void setAppOpsLocked(@NonNull PermissionsState permsState, + @NonNull PackageParser.Package pkg) { + for (int userId : UserManagerService.getInstance().getUserIds()) { + int numPerms = pkg.requestedPermissions.size(); + for (int i = 0; i < numPerms; i++) { + String permission = pkg.requestedPermissions.get(i); + + int op = permissionToOpCode(permission); + if (op == OP_NONE) { + continue; + } + + // Runtime permissions are per uid, not per package, hence per package app-op + // modes should never have been set. It is possible to set them via the shell + // though. Revert such settings during boot to get the device back into a good + // state. + LocalServices.getService(AppOpsManagerInternal.class).setAllPkgModesToDefault( + op, getUid(userId, getAppId(pkg.applicationInfo.uid))); + + // For pre-M apps the runtime permission do not store the state + if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + continue; + } + + PermissionState state = permsState.getRuntimePermissionState(permission, userId); + if (state == null) { + continue; + } + + // Adjust app-op mods for foreground/background permissions. If an package used to + // have both fg and bg permission granted and it lost the bg permission during an + // upgrade the app-op mode should get downgraded to foreground. + if (state.isGranted()) { + BasePermission bp = mSettings.getPermission(permission); + + if (bp != null && bp.perm != null && bp.perm.info != null + && bp.perm.info.backgroundPermission != null) { + PermissionState bgState = permsState.getRuntimePermissionState( + bp.perm.info.backgroundPermission, userId); + + setAppOpMode(permission, pkg, userId, bgState != null && bgState.isGranted() + ? MODE_ALLOWED : MODE_FOREGROUND); + } + } + } + } + } + private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { boolean allowed = false; final int NP = PackageParser.NEW_PERMISSIONS.length; diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING index 076c94c994a5..09bacd6ed332 100644 --- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING @@ -7,6 +7,17 @@ "include-filter": "com.google.android.permission.gts.DefaultPermissionGrantPolicyTest" } ] + }, + { + "name": "CtsPermissionTestCases", + "options": [ + { + "include-filter": "android.permission.cts.BackgroundPermissionsTest" + }, + { + "include-filter": "android.permission.cts.SplitPermissionTest" + } + ] } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java new file mode 100644 index 000000000000..a2c8dace9510 --- /dev/null +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.attention.AttentionManagerInternal; +import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.io.PrintWriter; + +/** + * Class responsible for checking if the user is currently paying attention to the phone and + * notifying {@link PowerManagerService} that user activity should be renewed. + * + * This class also implements a limit of how long the extension should be, to avoid security + * issues where the device would never be locked. + */ +public class AttentionDetector { + + private static final String TAG = "AttentionDetector"; + private static final boolean DEBUG = false; + + /** + * Invoked whenever user attention is detected. + */ + private final Runnable mOnUserAttention; + + /** + * The maximum time, in millis, that the phone can stay unlocked because of attention events, + * triggered by any user. + */ + @VisibleForTesting + protected long mMaximumExtensionMillis; + + private final Object mLock; + + /** + * {@link android.service.attention.AttentionService} API timeout. + */ + private long mMaxAttentionApiTimeoutMillis; + + /** + * Last known user activity. + */ + private long mLastUserActivityTime; + + @VisibleForTesting + protected AttentionManagerInternal mAttentionManager; + + /** + * If we're currently waiting for an attention callback + */ + private boolean mRequested; + + /** + * Current wakefulness of the device. {@see PowerManagerInternal} + */ + private int mWakefulness; + + @VisibleForTesting + final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() { + + @Override + public void onSuccess(int requestCode, int result, long timestamp) { + Slog.v(TAG, "onSuccess: " + requestCode + ", " + result + + " - current requestCode: " + getRequestCode()); + synchronized (mLock) { + if (requestCode == getRequestCode() && mRequested) { + mRequested = false; + if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + if (DEBUG) Slog.d(TAG, "Device slept before receiving callback."); + return; + } + if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) { + mOnUserAttention.run(); + } + } + } + } + + @Override + public void onFailure(int requestCode, int error) { + Slog.i(TAG, "Failed to check attention: " + error); + synchronized (mLock) { + if (requestCode == getRequestCode()) { + mRequested = false; + } + } + } + }; + + public AttentionDetector(Runnable onUserAttention, Object lock) { + mOnUserAttention = onUserAttention; + mLock = lock; + } + + public void systemReady(Context context) { + mAttentionManager = LocalServices.getService(AttentionManagerInternal.class); + mMaximumExtensionMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionMaximumExtension); + mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionApiTimeout); + } + + public long updateUserActivity(long nextScreenDimming) { + if (!isAttentionServiceSupported()) { + return nextScreenDimming; + } + + final long now = SystemClock.uptimeMillis(); + final long whenToCheck = nextScreenDimming - getAttentionTimeout(); + final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis; + if (now < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now)); + } + return nextScreenDimming; + } else if (whenToStopExtending < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Let device sleep to avoid false results and improve security " + + (whenToCheck - whenToStopExtending)); + } + return nextScreenDimming; + } else if (mRequested) { + if (DEBUG) { + Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode()); + } + return whenToCheck; + } + + // Ideally we should attribute mRequested to the result of #checkAttention, but the + // callback might arrive before #checkAttention returns (if there are cached results.) + // This means that we must assume that the request was successful, and then cancel it + // afterwards if AttentionManager couldn't deliver it. + mRequested = true; + final boolean sent = mAttentionManager.checkAttention(getRequestCode(), + getAttentionTimeout(), mCallback); + if (!sent) { + mRequested = false; + } + + Slog.v(TAG, "Checking user attention with request code: " + getRequestCode()); + return whenToCheck; + } + + /** + * Handles user activity by cancelling any pending attention requests and keeping track of when + * the activity happened. + * + * @param eventTime Activity time, in uptime millis. + * @param event Activity type as defined in {@link PowerManager}. + * @return 0 when activity was ignored, 1 when handled, -1 when invalid. + */ + public int onUserActivity(long eventTime, int event) { + switch (event) { + case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + return 0; + case PowerManager.USER_ACTIVITY_EVENT_OTHER: + case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: + cancelCurrentRequestIfAny(); + mLastUserActivityTime = eventTime; + return 1; + default: + if (DEBUG) { + Slog.d(TAG, "Attention not reset. Unknown activity event: " + event); + } + return -1; + } + } + + public void onWakefulnessChangeStarted(int wakefulness) { + mWakefulness = wakefulness; + if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + cancelCurrentRequestIfAny(); + } + } + + private void cancelCurrentRequestIfAny() { + if (mRequested) { + mAttentionManager.cancelAttentionCheck(getRequestCode()); + mRequested = false; + } + } + + @VisibleForTesting + int getRequestCode() { + return (int) (mLastUserActivityTime % Integer.MAX_VALUE); + } + + @VisibleForTesting + long getAttentionTimeout() { + return mMaxAttentionApiTimeoutMillis; + } + + /** + * {@see AttentionManagerInternal#isAttentionServiceSupported} + */ + @VisibleForTesting + boolean isAttentionServiceSupported() { + return mAttentionManager.isAttentionServiceSupported(); + } + + public void dump(PrintWriter pw) { + pw.print("AttentionDetector:"); + pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis); + pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis); + pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime); + pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported()); + pw.print(" mRequested=" + mRequested); + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index a02787308246..3be64802c9fc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -229,6 +229,7 @@ public final class PowerManagerService extends SystemService private final BatterySaverController mBatterySaverController; private final BatterySaverStateMachine mBatterySaverStateMachine; private final BatterySavingStats mBatterySavingStats; + private final AttentionDetector mAttentionDetector; private final BinderService mBinderService; private final LocalService mLocalService; private final NativeWrapper mNativeWrapper; @@ -736,6 +737,7 @@ public final class PowerManagerService extends SystemService mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mBatterySavingStats = new BatterySavingStats(mLock); mBatterySaverPolicy = @@ -804,6 +806,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); + mAttentionDetector.systemReady(mContext); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -1326,6 +1329,16 @@ public final class PowerManagerService extends SystemService } } + private void onUserAttention() { + synchronized (mLock) { + if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */, + Process.SYSTEM_UID)) { + updatePowerStateLocked(); + } + } + } + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime @@ -1346,6 +1359,7 @@ public final class PowerManagerService extends SystemService } mNotifier.onUserActivity(event, uid); + mAttentionDetector.onUserActivity(eventTime, event); if (mUserInactiveOverrideFromWindowManager) { mUserInactiveOverrideFromWindowManager = false; @@ -1593,6 +1607,7 @@ public final class PowerManagerService extends SystemService if (mNotifier != null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason); } + mAttentionDetector.onWakefulnessChangeStarted(wakefulness); } } @@ -2085,6 +2100,10 @@ public final class PowerManagerService extends SystemService nextTimeout = -1; } + if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) { + nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout); + } + if (nextProfileTimeout > 0) { nextTimeout = Math.min(nextTimeout, nextProfileTimeout); } @@ -3477,6 +3496,7 @@ public final class PowerManagerService extends SystemService mBatterySaverPolicy.dump(pw); mBatterySaverStateMachine.dump(pw); + mAttentionDetector.dump(pw); pw.println(); final int numProfiles = mProfilePowerState.size(); diff --git a/services/core/java/com/android/server/role/FinancialSmsManager.java b/services/core/java/com/android/server/role/FinancialSmsManager.java new file mode 100644 index 000000000000..2ec39931789a --- /dev/null +++ b/services/core/java/com/android/server/role/FinancialSmsManager.java @@ -0,0 +1,218 @@ +/* + * 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.role; + +import android.Manifest; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.sms.FinancialSmsService; +import android.service.sms.IFinancialSmsService; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * This class binds to {@code FinancialSmsService}. + */ +final class FinancialSmsManager { + + private static final String TAG = "FinancialSmsManager"; + + private final Context mContext; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private ServiceConnection mServiceConnection; + + @GuardedBy("mLock") + private IFinancialSmsService mRemoteService; + + @GuardedBy("mLock") + private ArrayList<Command> mQueuedCommands; + + FinancialSmsManager(Context context) { + mContext = context; + } + + @Nullable + ServiceInfo getServiceInfo() { + final String packageName = + mContext.getPackageManager().getServicesSystemSharedLibraryPackageName(); + if (packageName == null) { + Slog.w(TAG, "no external services package!"); + return null; + } + + final Intent intent = new Intent(FinancialSmsService.ACTION_FINANCIAL_SERVICE_INTENT); + intent.setPackage(packageName); + final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, + PackageManager.GET_SERVICES); + if (resolveInfo == null || resolveInfo.serviceInfo == null) { + Slog.w(TAG, "No valid components found."); + return null; + } + return resolveInfo.serviceInfo; + } + + @Nullable + private ComponentName getServiceComponentName() { + final ServiceInfo serviceInfo = getServiceInfo(); + if (serviceInfo == null) return null; + + final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); + if (!Manifest.permission.BIND_FINANCIAL_SMS_SERVICE.equals(serviceInfo.permission)) { + Slog.w(TAG, name.flattenToShortString() + " does not require permission " + + Manifest.permission.BIND_FINANCIAL_SMS_SERVICE); + return null; + } + + return name; + } + + void reset() { + synchronized (mLock) { + if (mServiceConnection != null) { + mContext.unbindService(mServiceConnection); + mServiceConnection = null; + } else { + Slog.d(TAG, "reset(): service is not bound. Do nothing."); + } + } + } + + /** + * Run a command, starting the service connection if necessary. + */ + private void connectAndRun(@NonNull Command command) { + synchronized (mLock) { + if (mRemoteService != null) { + try { + command.run(mRemoteService); + } catch (RemoteException e) { + Slog.w(TAG, "exception calling service: " + e); + } + return; + } else { + if (mQueuedCommands == null) { + mQueuedCommands = new ArrayList<>(1); + } + mQueuedCommands.add(command); + // If we're already connected, don't create a new connection, just leave - the + // command will be run when the service connects + if (mServiceConnection != null) return; + } + + // Create the connection + mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mRemoteService = IFinancialSmsService.Stub.asInterface(service); + if (mQueuedCommands != null) { + final int size = mQueuedCommands.size(); + for (int i = 0; i < size; i++) { + final Command queuedCommand = mQueuedCommands.get(i); + try { + queuedCommand.run(mRemoteService); + } catch (RemoteException e) { + Slog.w(TAG, "exception calling " + name + ": " + e); + } + } + mQueuedCommands = null; + } + } + } + + @Override + @MainThread + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + mRemoteService = null; + } + } + + @Override + public void onBindingDied(ComponentName name) { + synchronized (mLock) { + mRemoteService = null; + } + } + + @Override + public void onNullBinding(ComponentName name) { + synchronized (mLock) { + mRemoteService = null; + } + } + }; + + final ComponentName component = getServiceComponentName(); + if (component != null) { + final Intent intent = new Intent(); + intent.setComponent(component); + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE, + UserHandle.getUserHandleForUid(UserHandle.getCallingUserId())); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + + void getSmsMessages(RemoteCallback callback, @Nullable Bundle params) { + connectAndRun((service) -> service.getSmsMessages(callback, params)); + } + + void dump(String prefix, PrintWriter pw) { + final ComponentName impl = getServiceComponentName(); + pw.print(prefix); pw.print("User ID: "); pw.println(UserHandle.getCallingUserId()); + pw.print(prefix); pw.print("Queued commands: "); + if (mQueuedCommands == null) { + pw.println("N/A"); + } else { + pw.println(mQueuedCommands.size()); + } + pw.print(prefix); pw.print("Implementation: "); + if (impl == null) { + pw.println("N/A"); + return; + } + pw.println(impl.flattenToShortString()); + } + + private interface Command { + void run(IFinancialSmsService service) throws RemoteException; + } +} diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index 1c7596b80fd7..7d8c7c9ee0d5 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -33,16 +33,24 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; +import android.database.CursorWindow; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; +import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.service.sms.FinancialSmsService; +import android.telephony.IFinancialSmsCallback; import android.text.TextUtils; import android.util.ArraySet; import android.util.PackageUtils; @@ -620,5 +628,51 @@ public class RoleManagerService extends SystemService implements RoleUserState.C dumpOutputStream.flush(); } + + /** + * Get filtered SMS messages for financial app. + */ + @Override + public void getSmsMessagesForFinancialApp( + String callingPkg, Bundle params, IFinancialSmsCallback callback) { + int mode = PermissionChecker.checkCallingOrSelfPermission( + getContext(), + AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS); + + if (mode == PermissionChecker.PERMISSION_GRANTED) { + FinancialSmsManager financialSmsManager = new FinancialSmsManager(getContext()); + financialSmsManager.getSmsMessages(new RemoteCallback((result) -> { + CursorWindow messages = null; + if (result == null) { + Slog.w(LOG_TAG, "result is null."); + } else { + messages = result.getParcelable(FinancialSmsService.EXTRA_SMS_MSGS); + } + try { + callback.onGetSmsMessagesForFinancialApp(messages); + } catch (RemoteException e) { + // do nothing + } + }), params); + } else { + try { + callback.onGetSmsMessagesForFinancialApp(null); + } catch (RemoteException e) { + // do nothing + } + } + } + + private int getUidForPackage(String packageName) { + long ident = Binder.clearCallingIdentity(); + try { + return getContext().getPackageManager().getApplicationInfo(packageName, + PackageManager.MATCH_ANY_USER).uid; + } catch (NameNotFoundException nnfe) { + return -1; + } finally { + Binder.restoreCallingIdentity(ident); + } + } } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 8b4c410000bb..e59228a16fb1 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -28,7 +28,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; -import android.content.pm.StringParceledListSlice; import android.content.pm.VersionedPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; @@ -56,12 +55,10 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; /** * Implementation of service that manages APK level rollbacks. @@ -200,48 +197,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } @Override - public RollbackInfo getAvailableRollback(String packageName) { + public ParceledListSlice getAvailableRollbacks() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_ROLLBACKS, - "getAvailableRollback"); + "getAvailableRollbacks"); - RollbackData data = getRollbackForPackage(packageName); - if (data == null) { - return null; - } - - // Note: The rollback for the package ought to be for the currently - // installed version, otherwise the rollback data is out of date. In - // that rare case, we'll check when we execute the rollback whether - // it's out of date or not, so no need to check package versions here. - - for (PackageRollbackInfo info : data.packages) { - if (info.getPackageName().equals(packageName)) { - // TODO: Once the RollbackInfo API supports info about - // dependant packages, add that info here. - return new RollbackInfo(data.rollbackId, info); - } - } - return null; - } - - @Override - public StringParceledListSlice getPackagesWithAvailableRollbacks() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_ROLLBACKS, - "getPackagesWithAvailableRollbacks"); - - final Set<String> packageNames = new HashSet<>(); synchronized (mLock) { ensureRollbackDataLoadedLocked(); + List<RollbackInfo> rollbacks = new ArrayList<>(); for (int i = 0; i < mAvailableRollbacks.size(); ++i) { RollbackData data = mAvailableRollbacks.get(i); - for (PackageRollbackInfo info : data.packages) { - packageNames.add(info.getPackageName()); - } + rollbacks.add(new RollbackInfo(data.rollbackId, data.packages)); } + return new ParceledListSlice<>(rollbacks); } - return new StringParceledListSlice(new ArrayList<>(packageNames)); } @Override @@ -258,7 +227,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } @Override - public void executeRollback(RollbackInfo rollback, String callerPackageName, + public void commitRollback(int rollbackId, String callerPackageName, IntentSender statusReceiver) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_ROLLBACKS, @@ -269,28 +238,21 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { appOps.checkPackage(callingUid, callerPackageName); getHandler().post(() -> - executeRollbackInternal(rollback, callerPackageName, statusReceiver)); + commitRollbackInternal(rollbackId, callerPackageName, statusReceiver)); } /** - * Performs the actual work to execute a rollback. + * Performs the actual work to commit a rollback. * The work is done on the current thread. This may be a long running * operation. */ - private void executeRollbackInternal(RollbackInfo rollback, + private void commitRollbackInternal(int rollbackId, String callerPackageName, IntentSender statusReceiver) { - String targetPackageName = rollback.targetPackage.getPackageName(); - Log.i(TAG, "Initiating rollback of " + targetPackageName); + Log.i(TAG, "Initiating rollback"); - // Get the latest RollbackData for the target package. - final RollbackData data = getRollbackForPackage(targetPackageName); + RollbackData data = getRollbackForId(rollbackId); 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 unavailable"); return; } @@ -335,14 +297,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManager pm = context.getPackageManager(); try { PackageInstaller packageInstaller = pm.getPackageInstaller(); - String installerPackageName = pm.getInstallerPackageName(targetPackageName); - if (installerPackageName == null) { - sendFailure(statusReceiver, "Cannot find installer package"); - return; - } PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); - parentParams.setInstallerPackageName(installerPackageName); parentParams.setAllowDowngrade(true); parentParams.setMultiPackage(); int parentSessionId = packageInstaller.createSession(parentParams); @@ -351,6 +307,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); + String installerPackageName = pm.getInstallerPackageName(info.getPackageName()); + if (installerPackageName == null) { + sendFailure(statusReceiver, "Cannot find installer package"); + return; + } params.setInstallerPackageName(installerPackageName); params.setAllowDowngrade(true); int sessionId = packageInstaller.createSession(params); @@ -389,10 +350,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return; } - addRecentlyExecutedRollback(rollback); + addRecentlyExecutedRollback( + new RollbackInfo(data.rollbackId, data.packages)); sendSuccess(statusReceiver); - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); + Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); // TODO: This call emits the warning "Calling a method in the // system process without a qualified user". Fix that. @@ -406,7 +368,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { data.inProgress = true; parentSession.commit(receiver.getIntentSender()); } catch (IOException e) { - Log.e(TAG, "Unable to roll back " + targetPackageName, e); + Log.e(TAG, "Rollback failed", e); sendFailure(statusReceiver, "IOException: " + e.toString()); return; } @@ -537,9 +499,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { boolean changed = false; while (iter.hasNext()) { RollbackInfo rollback = iter.next(); - if (packageName.equals(rollback.targetPackage.getPackageName())) { - iter.remove(); - changed = true; + for (PackageRollbackInfo info : rollback.getPackages()) { + if (packageName.equals(info.getPackageName())) { + iter.remove(); + changed = true; + break; + } } } @@ -935,6 +900,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return null; } + /* + * Returns the RollbackData, if any, for an available rollback with the + * given rollbackId. + */ + private RollbackData getRollbackForId(int rollbackId) { + synchronized (mLock) { + // TODO: Have ensureRollbackDataLoadedLocked return the list of + // available rollbacks, to hopefully avoid forgetting to call it? + ensureRollbackDataLoadedLocked(); + for (int i = 0; i < mAvailableRollbacks.size(); ++i) { + RollbackData data = mAvailableRollbacks.get(i); + if (data.rollbackId == rollbackId) { + return data; + } + } + } + return null; + } + @GuardedBy("mLock") private int allocateRollbackIdLocked() throws IOException { int n = 0; diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 1f2f1ccd7383..fcae61895b36 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -19,6 +19,7 @@ package com.android.server.rollback; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInstaller; +import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.Handler; @@ -26,6 +27,7 @@ import android.os.HandlerThread; import com.android.server.PackageWatchdog; import com.android.server.PackageWatchdog.PackageHealthObserver; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import java.util.List; @@ -38,10 +40,12 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; private Context mContext; + private RollbackManager mRollbackManager; private Handler mHandler; RollbackPackageHealthObserver(Context context) { mContext = context; + mRollbackManager = mContext.getSystemService(RollbackManager.class); HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); handlerThread.start(); mHandler = handlerThread.getThreadHandler(); @@ -49,16 +53,48 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @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; + public int onHealthCheckFailed(String packageName) { + RollbackInfo rollback = getAvailableRollback(packageName); + if (rollback == null) { + // Don't handle the notification, no rollbacks available for the package + return PackageHealthObserverImpact.USER_IMPACT_NONE; } - // Don't handle the notification, no rollbacks available - return false; + // Rollback is available, we may get a callback into #execute + return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + } + + @Override + public boolean execute(String packageName) { + RollbackInfo rollback = getAvailableRollback(packageName); + if (rollback == null) { + // Expected a rollback to be available, what happened? + return false; + } + + // TODO(zezeozue): Only rollback if rollback version == failed package version + LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> { + 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 + } + }); + + // TODO(zezeozue): Log initiated metrics + mHandler.post(() -> + mRollbackManager.commitRollback(rollback.getRollbackId(), + rollbackReceiver.getIntentSender())); + // Assume rollback executed successfully + return true; + } + + @Override + public String getName() { + return NAME; } /** @@ -69,26 +105,15 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve 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 + private RollbackInfo getAvailableRollback(String packageName) { + for (RollbackInfo rollback : mRollbackManager.getAvailableRollbacks()) { + for (PackageRollbackInfo packageRollback : rollback.getPackages()) { + if (packageName.equals(packageRollback.getPackageName())) { + // TODO(zezeozue): Only rollback if rollback version == failed package version + return rollback; } - }); - }); - manager.executeRollback(rollback, rollbackReceiver.getIntentSender()); - } - - @Override - public String getName() { - return NAME; + } + } + return null; } } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 7738be9d32bb..3b24b3e9a444 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -114,13 +114,9 @@ class RollbackStore { for (int i = 0; i < array.length(); ++i) { JSONObject element = array.getJSONObject(i); int rollbackId = element.getInt("rollbackId"); - String packageName = element.getString("packageName"); - long higherVersionCode = element.getLong("higherVersionCode"); - long lowerVersionCode = element.getLong("lowerVersionCode"); - PackageRollbackInfo target = new PackageRollbackInfo( - new VersionedPackage(packageName, higherVersionCode), - new VersionedPackage(packageName, lowerVersionCode)); - RollbackInfo rollback = new RollbackInfo(rollbackId, target); + List<PackageRollbackInfo> packages = packageRollbackInfosFromJson( + element.getJSONArray("packages")); + RollbackInfo rollback = new RollbackInfo(rollbackId, packages); recentlyExecutedRollbacks.add(rollback); } } catch (IOException | JSONException e) { @@ -155,18 +151,8 @@ class RollbackStore { void saveAvailableRollback(RollbackData data) throws IOException { try { JSONObject dataJson = new JSONObject(); - JSONArray packagesJson = new JSONArray(); - for (PackageRollbackInfo info : data.packages) { - JSONObject infoJson = new JSONObject(); - infoJson.put("packageName", info.getPackageName()); - infoJson.put("higherVersionCode", - info.getVersionRolledBackFrom().getLongVersionCode()); - infoJson.put("lowerVersionCode", - info.getVersionRolledBackTo().getVersionCode()); - packagesJson.put(infoJson); - } dataJson.put("rollbackId", data.rollbackId); - dataJson.put("packages", packagesJson); + dataJson.put("packages", toJson(data.packages)); dataJson.put("timestamp", data.timestamp.toString()); PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json")); @@ -200,11 +186,7 @@ class RollbackStore { RollbackInfo rollback = recentlyExecutedRollbacks.get(i); JSONObject element = new JSONObject(); element.put("rollbackId", rollback.getRollbackId()); - element.put("packageName", rollback.targetPackage.getPackageName()); - element.put("higherVersionCode", - rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode()); - element.put("lowerVersionCode", - rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode()); + element.put("packages", toJson(rollback.getPackages())); array.put(element); } @@ -229,18 +211,7 @@ class RollbackStore { int rollbackId = dataJson.getInt("rollbackId"); RollbackData data = new RollbackData(rollbackId, backupDir); - - JSONArray packagesJson = dataJson.getJSONArray("packages"); - for (int i = 0; i < packagesJson.length(); ++i) { - JSONObject infoJson = packagesJson.getJSONObject(i); - String packageName = infoJson.getString("packageName"); - long higherVersionCode = infoJson.getLong("higherVersionCode"); - long lowerVersionCode = infoJson.getLong("lowerVersionCode"); - data.packages.add(new PackageRollbackInfo( - new VersionedPackage(packageName, higherVersionCode), - new VersionedPackage(packageName, lowerVersionCode))); - } - + data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages"))); data.timestamp = Instant.parse(dataJson.getString("timestamp")); return data; } catch (JSONException | DateTimeParseException e) { @@ -248,6 +219,40 @@ class RollbackStore { } } + private JSONObject toJson(PackageRollbackInfo info) throws JSONException { + JSONObject json = new JSONObject(); + json.put("packageName", info.getPackageName()); + json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode()); + json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode()); + return json; + } + + private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException { + String packageName = json.getString("packageName"); + long higherVersionCode = json.getLong("higherVersionCode"); + long lowerVersionCode = json.getLong("lowerVersionCode"); + return new PackageRollbackInfo( + new VersionedPackage(packageName, higherVersionCode), + new VersionedPackage(packageName, lowerVersionCode)); + } + + private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException { + JSONArray json = new JSONArray(); + for (PackageRollbackInfo info : infos) { + json.put(toJson(info)); + } + return json; + } + + private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json) + throws JSONException { + List<PackageRollbackInfo> infos = new ArrayList<>(); + for (int i = 0; i < json.length(); ++i) { + infos.add(packageRollbackInfoFromJson(json.getJSONObject(i))); + } + return infos; + } + /** * Deletes a file completely. * If the file is a directory, its contents are deleted as well. diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index acede7d4fa90..c6d2870a24c9 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -246,6 +246,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader; + private long mDebugElapsedClockPreviousValue = 0; + private long mDebugElapsedClockPullCount = 0; + private long mDebugFailingElapsedClockPreviousValue = 0; + private long mDebugFailingElapsedClockPullCount = 0; private BatteryStatsHelper mBatteryStatsHelper = null; private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; @@ -1726,6 +1730,56 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + private void pullDebugElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + final long elapsedMillis = SystemClock.elapsedRealtime(); + final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue; + + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeLong(mDebugElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(clockDiffMillis); + e.writeInt(1 /* always set */); + pulledData.add(e); + + if (mDebugElapsedClockPullCount % 2 == 1) { + StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e2.writeLong(mDebugElapsedClockPullCount); + e2.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e2.writeLong(elapsedMillis); + e2.writeLong(clockDiffMillis); + e2.writeInt(2 /* set on odd pulls */); + pulledData.add(e2); + } + + mDebugElapsedClockPullCount++; + mDebugElapsedClockPreviousValue = elapsedMillis; + } + + private void pullDebugFailingElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + final long elapsedMillis = SystemClock.elapsedRealtime(); + // Fails every 10 buckets. + if (mDebugFailingElapsedClockPullCount++ % 10 == 0) { + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + throw new RuntimeException("Failing debug elapsed clock"); + } + + e.writeLong(mDebugFailingElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(mDebugFailingElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue); + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + pulledData.add(e); + } + /** * Pulls various data. */ @@ -1843,7 +1897,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret); break; } - case StatsLog.NUM_FINGERPRINTS: { + case StatsLog.NUM_FINGERPRINTS_ENROLLED: { pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret); break; } @@ -1892,6 +1946,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullTemperature(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEBUG_ELAPSED_CLOCK: { + pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: { + pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index a66f0caee56c..b9b5aae03587 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -100,4 +100,11 @@ public interface StatusBarManagerInternal { * @param rotation rotation suggestion */ void onProposedRotationChanged(int rotation, boolean isValid); + + /** + * Notifies System UI that the display is ready to show system decorations. + * + * @param displayId display ID + */ + void onDisplayReady(int displayId); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 8d2bab43875d..7e87c29e7a58 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -443,6 +443,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } catch (RemoteException ex) {} } } + + @Override + public void onDisplayReady(int displayId) { + if (mBar != null) { + try { + mBar.onDisplayReady(displayId); + } catch (RemoteException ex) { } + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index 23c042a57ac8..4adce586e9a5 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -66,6 +66,7 @@ public class TestHarnessModeService extends SystemService { private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; + private boolean mShouldSetUpTestHarnessMode; public TestHarnessModeService(Context context) { super(context); @@ -96,6 +97,7 @@ public class TestHarnessModeService extends SystemService { // There's no data to apply, so leave it as-is. return; } + mShouldSetUpTestHarnessMode = true; PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData); SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0"); @@ -124,6 +126,9 @@ public class TestHarnessModeService extends SystemService { } private void disableAutoSync() { + if (!mShouldSetUpTestHarnessMode) { + return; + } UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); ContentResolver .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier()); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 69040a2ea974..b0ef8a0d4209 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -469,6 +469,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ private void extractColors(WallpaperData wallpaper) { String cropFile = null; + boolean defaultImageWallpaper = false; int wallpaperId; if (wallpaper.equals(mFallbackWallpaper)) { @@ -482,6 +483,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub || wallpaper.wallpaperComponent == null; if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) { cropFile = wallpaper.cropFile.getAbsolutePath(); + } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) { + defaultImageWallpaper = true; } wallpaperId = wallpaper.wallpaperId; } @@ -493,6 +496,25 @@ public class WallpaperManagerService extends IWallpaperManager.Stub colors = WallpaperColors.fromBitmap(bitmap); bitmap.recycle(); } + } else if (defaultImageWallpaper) { + // There is no crop and source file because this is default image wallpaper. + try (final InputStream is = + WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) { + if (is != null) { + try { + final BitmapFactory.Options options = new BitmapFactory.Options(); + final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + if (bitmap != null) { + colors = WallpaperColors.fromBitmap(bitmap); + bitmap.recycle(); + } + } catch (OutOfMemoryError e) { + Slog.w(TAG, "Can't decode default wallpaper stream", e); + } + } + } catch (IOException e) { + Slog.w(TAG, "Can't close default wallpaper stream", e); + } } if (colors == null) { diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 65d66f44b5dd..e817dd47e756 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -558,22 +558,26 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** - * Pause all activities in either all of the stacks or just the back stacks. + * Pause all activities in either all of the stacks or just the back stacks. This is done before + * resuming a new activity and to make sure that previously active activities are + * paused in stacks that are no longer visible or in pinned windowing mode. This does not + * pause activities in visible stacks, so if an activity is launched within the same stack/task, + * then we should explicitly pause that stack's top activity. * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @param resuming The resuming activity. * @param dontWait The resuming activity isn't going to wait for all activities to be paused * before resuming. - * @return true if any activity was paused as a result of this call. + * @return {@code true} if any activity was paused as a result of this call. */ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); - // TODO(b/111541062): Check if resumed activity on this display instead - if (!mRootActivityContainer.isTopDisplayFocusedStack(stack) - && stack.getResumedActivity() != null) { + final ActivityRecord resumedActivity = stack.getResumedActivity(); + if (resumedActivity != null + && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + - " mResumedActivity=" + stack.getResumedActivity()); + " mResumedActivity=" + resumedActivity); someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, dontWait); } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 102318262798..2d89bc7c5613 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -544,6 +544,16 @@ class ActivityMetricsLogger { mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget(); } + private boolean hasVisibleNonFinishingActivity(TaskRecord t) { + for (int i = t.mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = t.mActivities.get(i); + if (r.visible && !r.finishing) { + return true; + } + } + return false; + } + private void checkVisibility(TaskRecord t, ActivityRecord r) { synchronized (mSupervisor.mService.mGlobalLock) { @@ -552,7 +562,7 @@ class ActivityMetricsLogger { // If we have an active transition that's waiting on a certain activity that will be // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary. - if (info != null && !t.isVisible()) { + if (info != null && !hasVisibleNonFinishingActivity(t)) { if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible" + " activity=" + r); logAppTransitionCancel(info); @@ -841,13 +851,11 @@ class ActivityMetricsLogger { Log.i(TAG, sb.toString()); } - void logActivityStart(Intent intent, WindowProcessController callerApp, ActivityRecord r, + void logAbortedBgActivityStart(Intent intent, WindowProcessController callerApp, int callingUid, String callingPackage, int callingUidProcState, boolean callingUidHasAnyVisibleWindow, int realCallingUid, int realCallingUidProcState, boolean realCallingUidHasAnyVisibleWindow, - int targetUid, String targetPackage, int targetUidProcState, - boolean targetUidHasAnyVisibleWindow, String targetWhitelistTag, boolean comingFromPendingIntent) { final long nowElapsed = SystemClock.elapsedRealtime(); @@ -865,13 +873,6 @@ class ActivityMetricsLogger { processStateAmToProto(realCallingUidProcState)); builder.addTaggedData(FIELD_REAL_CALLING_UID_HAS_ANY_VISIBLE_WINDOW, realCallingUidHasAnyVisibleWindow ? 1 : 0); - builder.addTaggedData(FIELD_TARGET_UID, targetUid); - builder.addTaggedData(FIELD_TARGET_PACKAGE_NAME, targetPackage); - builder.addTaggedData(FIELD_TARGET_UID_PROC_STATE, - processStateAmToProto(targetUidProcState)); - builder.addTaggedData(FIELD_TARGET_UID_HAS_ANY_VISIBLE_WINDOW, - targetUidHasAnyVisibleWindow ? 1 : 0); - builder.addTaggedData(FIELD_TARGET_WHITELIST_TAG, targetWhitelistTag); builder.addTaggedData(FIELD_COMING_FROM_PENDING_INTENT, comingFromPendingIntent ? 1 : 0); if (intent != null) { builder.addTaggedData(FIELD_INTENT_ACTION, intent.getAction()); @@ -904,35 +905,6 @@ class ActivityMetricsLogger { (nowUptime - callerApp.getWhenUnimportant())); } } - if (r != null) { - builder.addTaggedData(FIELD_TARGET_SHORT_COMPONENT_NAME, r.shortComponentName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_LAUNCH_MODE, r.info.launchMode); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_TARGET_ACTIVITY, r.info.targetActivity); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_FLAGS, r.info.flags); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_REAL_ACTIVITY, - r.mActivityComponent.toShortString()); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_SHORT_COMPONENT_NAME, r.shortComponentName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_PROCESS_NAME, r.processName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_FULLSCREEN, r.fullscreen ? 1 : 0); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_NO_DISPLAY, r.noDisplay ? 1 : 0); - if (r.lastVisibleTime != 0) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_VISIBLE, - (nowUptime - r.lastVisibleTime)); - } - if (r.resultTo != null) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_PKG_NAME, - r.resultTo.packageName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_SHORT_COMPONENT_NAME, - r.resultTo.shortComponentName); - } - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE, r.visible ? 1 : 0); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE_IGNORING_KEYGUARD, - r.visibleIgnoringKeyguard ? 1 : 0); - if (r.lastLaunchTime != 0) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_LAUNCH, - (nowUptime - r.lastLaunchTime)); - } - } mMetricsLogger.write(builder); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b8634d88319a..8f39f3d6e463 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -161,6 +161,7 @@ import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PipModeChangeItem; import android.app.servertransaction.ResumeActivityItem; +import android.app.servertransaction.TopResumedActivityChangeItem; import android.app.servertransaction.WindowVisibilityItem; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; @@ -692,6 +693,26 @@ final class ActivityRecord extends ConfigurationContainer { } } + void scheduleTopResumedActivityChanged(boolean onTop) { + if (!attachedToProcess()) { + if (DEBUG_CONFIGURATION) { + Slog.w(TAG, "Can't report activity position update - client not running" + + ", activityRecord=" + this); + } + return; + } + try { + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Sending position change to " + this + ", onTop: " + onTop); + } + + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + TopResumedActivityChangeItem.obtain(onTop)); + } catch (RemoteException e) { + // If process died, whatever. + } + } + void updateMultiWindowMode() { if (task == null || task.getStack() == null || !attachedToProcess()) { return; @@ -1946,30 +1967,90 @@ final class ActivityRecord extends ConfigurationContainer { try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, WindowVisibilityItem.obtain(true /* showWindow */)); - if (shouldPauseWhenBecomingVisible()) { - // An activity must be in the {@link PAUSING} state for the system to validate - // the move to {@link PAUSED}. - setState(PAUSING, "makeVisibleIfNeeded"); + makeActiveIfNeeded(null /* activeActivity*/); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); + } + } + + /** + * Make activity resumed or paused if needed. + * @param activeActivity an activity that is resumed or just completed pause action. + * We won't change the state of this activity. + */ + boolean makeActiveIfNeeded(ActivityRecord activeActivity) { + if (shouldResumeActivity(activeActivity)) { + if (DEBUG_VISIBILITY) { + Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this); + } + return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */, + null /* options */); + } else if (shouldPauseActivity(activeActivity)) { + if (DEBUG_VISIBILITY) { + Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this); + } + // An activity must be in the {@link PAUSING} state for the system to validate + // the move to {@link PAUSED}. + setState(PAUSING, "makeVisibleIfNeeded"); + try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */)); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); } + return false; } - /** Check if activity should be moved to PAUSED state when it becomes visible. */ - private boolean shouldPauseWhenBecomingVisible() { - // If the activity is stopped or stopping, cycle to the paused state. We avoid doing + /** + * Check if activity should be moved to PAUSED state. The activity: + * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) + * - should be non-focusable + * - should not be currently pausing or paused + * @param activeActivity the activity that is active or just completed pause action. We won't + * resume if this activity is active. + */ + private boolean shouldPauseActivity(ActivityRecord activeActivity) { + return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED); + } + + /** + * Check if activity should be moved to RESUMED state. The activity: + * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) + * - should be focusable + * @param activeActivity the activity that is active or just completed pause action. We won't + * resume if this activity is active. + */ + private boolean shouldResumeActivity(ActivityRecord activeActivity) { + return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED); + } + + /** + * Check if activity is eligible to be made active (resumed of paused). The activity: + * - should be paused, stopped or stopping + * - should not be the currently active one or launching behind other tasks + * - should be either the topmost in task, or right below the top activity that is finishing + * If all of these conditions are not met at the same time, the activity cannot be made active. + */ + private boolean shouldMakeActive(ActivityRecord activeActivity) { + // If the activity is stopped, stopping, cycle to an active state. We avoid doing // this when there is an activity waiting to become translucent as the extra binder // calls will lead to noticeable jank. A later call to - // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper - // paused state. We also avoid doing this for the activity the stack supervisor - // considers the resumed activity, as normal means will bring the activity from STOPPED - // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles. - if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null - || isResumedActivityOnDisplay()) { + // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper + // active state. + if (!isState(RESUMED, PAUSED, STOPPED, STOPPING) + || getActivityStack().mTranslucentActivityWaiting != null) { + return false; + } + + if (this == activeActivity) { + return false; + } + + if (this.mLaunchTaskBehind) { + // This activity is being launched from behind, which means that it's not intended to be + // presented to user right now, even if it's set to be visible. return false; } @@ -1979,14 +2060,14 @@ final class ActivityRecord extends ConfigurationContainer { throw new IllegalStateException("Activity not found in its task"); } if (positionInTask == task.mActivities.size() - 1) { - // It's the topmost activity in the task - should become paused now + // It's the topmost activity in the task - should become resumed now return true; } // Check if activity above is finishing now and this one becomes the topmost in task. final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1); if (activityAbove.finishing && results == null) { - // We will only allow pausing if activity above wasn't launched for result. Otherwise it - // will cause this activity to resume before getting result. + // We will only allow making active if activity above wasn't launched for result. + // Otherwise it will cause this activity to resume before getting result. return true; } return false; @@ -3039,6 +3120,7 @@ final class ActivityRecord extends ConfigurationContainer { transaction.addCallback(callbackItem); transaction.setLifecycleStateRequest(lifecycleItem); mAtmService.getLifecycleManager().scheduleTransaction(transaction); + mRootActivityContainer.updateTopResumedActivityIfNeeded(); // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only // request resume if this activity is currently resumed, which implies we aren't // sleeping. diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3a754c411684..549567a182bb 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -357,6 +357,11 @@ class ActivityStack extends ConfigurationContainer { */ boolean mForceHidden = false; + /** + * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively + */ + boolean mInResumeTopActivity = false; + private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; private boolean mUpdateDisplayedBoundsDeferredCalled; @@ -1732,6 +1737,7 @@ class ActivityStack extends ConfigurationContainer { "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); + if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { @@ -2088,8 +2094,7 @@ class ActivityStack extends ConfigurationContainer { boolean aboveTop = top != null; final boolean stackShouldBeVisible = shouldBeVisible(starting); boolean behindFullscreenActivity = !stackShouldBeVisible; - boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this) - && (isInStackLocked(starting) == null); + boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null; final boolean isTopNotPinnedStack = isAttached() && getDisplay().isTopNotPinnedStack(this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -2150,6 +2155,10 @@ class ActivityStack extends ConfigurationContainer { if (r.handleAlreadyVisible()) { resumeNextActivity = false; } + + if (notifyClients) { + r.makeActiveIfNeeded(starting); + } } else { r.makeVisibleIfNeeded(starting, notifyClients); } @@ -2327,7 +2336,7 @@ class ActivityStack extends ConfigurationContainer { r.setVisible(true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, andResume, false); + mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); return true; } } @@ -2505,7 +2514,7 @@ class ActivityStack extends ConfigurationContainer { */ @GuardedBy("mService") boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { - if (mStackSupervisor.inResumeTopActivity) { + if (mInResumeTopActivity) { // Don't even start recursing. return false; } @@ -2513,8 +2522,9 @@ class ActivityStack extends ConfigurationContainer { boolean result = false; try { // Protect against recursion. - mStackSupervisor.inResumeTopActivity = true; + mInResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); + mRootActivityContainer.updateTopResumedActivityIfNeeded(); // When resuming the top activity, it may be necessary to pause the top activity (for // example, returning to the lock screen. We suppress the normal pause logic in @@ -2528,7 +2538,7 @@ class ActivityStack extends ConfigurationContainer { checkReadyForSleep(); } } finally { - mStackSupervisor.inResumeTopActivity = false; + mInResumeTopActivity = false; } return result; @@ -2561,7 +2571,7 @@ class ActivityStack extends ConfigurationContainer { // Find the next top-most activity to resume in this stack that is not finishing and is // focusable. If it is not focusable, we will fall into the case below to resume the // top activity in the next focusable task. - final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); final boolean hasRunningActivity = next != null; @@ -2649,6 +2659,12 @@ class ActivityStack extends ConfigurationContainer { if (!mRootActivityContainer.allPausedActivitiesComplete()) { if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing."); + + // Adding previous activity to the waiting visible list, or it would be stopped + // before top activity being visible. + if (prev != null && !next.nowVisible) { + mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev); + } return false; } @@ -2858,7 +2874,9 @@ class ActivityStack extends ConfigurationContainer { // the screen based on the new activity order. boolean notUpdated = true; - if (isFocusedStackOnDisplay()) { + // Activity should also be visible if set mLaunchTaskBehind to true (see + // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). + if (shouldBeVisible(next)) { // We have special rotation behavior when here is some active activity that // requests specific orientation or Keyguard is locked. Make sure all activity // visibilities are set correctly as well as the transition is updated if needed @@ -4087,6 +4105,12 @@ class ActivityStack extends ConfigurationContainer { mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); mRootActivityContainer.resumeFocusedStacksTopActivities(); + // If activity was not paused at this point - explicitly pause it to start finishing + // process. Finishing will be completed once it reports pause back. + if (r.isState(RESUMED) && mPausingActivity != null) { + startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */, + false /* dontWait */); + } return r; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 3a288ca5560d..c8a150beecfa 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -327,9 +327,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { */ PowerManager.WakeLock mGoingToSleep; - /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ - boolean inResumeTopActivity; - /** * Temporary rect used during docked stack resize calculation so we don't need to create a new * object each time. @@ -843,6 +840,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // Schedule transaction. mService.getLifecycleManager().scheduleTransaction(clientTransaction); + mRootActivityContainer.updateTopResumedActivityIfNeeded(); if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 && mService.mHasHeavyWeightFeature) { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 280709461c98..43c12064a3c1 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -179,7 +179,10 @@ public class ActivityStartController { .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; - if (mSupervisor.inResumeTopActivity) { + final ActivityDisplay display = + mService.mRootActivityContainer.getActivityDisplay(displayId); + final ActivityStack homeStack = display != null ? display.getHomeStack() : null; + if (homeStack != null && homeStack.mInResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. We need to schedule another resume. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 38580bc41f74..95a6f71fc44b 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -749,9 +749,15 @@ class ActivityStarter { boolean abortBackgroundStart = false; if (!abort) { - abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid, - callingPackage, realCallingUid, callerApp, originatingPendingIntent, - allowBackgroundActivityStart, intent); + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "shouldAbortBackgroundActivityStart"); + abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid, + callingPackage, realCallingUid, callerApp, originatingPendingIntent, + allowBackgroundActivityStart, intent); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled()); // TODO: remove this toast after feature development is done if (abortBackgroundStart) { @@ -903,12 +909,6 @@ class ActivityStarter { mService.onStartActivitySetDidAppSwitch(); mController.doPendingActivityLaunches(false); - // 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); } @@ -927,17 +927,31 @@ class ActivityStarter { return false; } // don't abort if the callingUid is in the foreground or is a persistent system process - final boolean isCallingUidForeground = mService.isUidForeground(callingUid); - final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( - callingUid); + final int callingUidProcState = mService.getUidStateLocked(callingUid); + final boolean callingUidHasAnyVisibleWindow = + mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid); + final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow + || callingUidProcState == ActivityManager.PROCESS_STATE_TOP; + final boolean isCallingUidPersistentSystemProcess = (callingUid == Process.SYSTEM_UID) + || callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; if (isCallingUidForeground || isCallingUidPersistentSystemProcess) { return false; } // take realCallingUid into consideration - final boolean isRealCallingUidForeground = mService.isUidForeground( - realCallingUid); - final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( - realCallingUid); + final int realCallingUidProcState = (callingUid == realCallingUid) + ? callingUidProcState + : mService.getUidStateLocked(realCallingUid); + final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid) + ? callingUidHasAnyVisibleWindow + : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid); + final boolean isRealCallingUidForeground = (callingUid == realCallingUid) + ? isCallingUidForeground + : realCallingUidHasAnyVisibleWindow + || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP; + final boolean isRealCallingUidPersistentSystemProcess = (callingUid == realCallingUid) + ? isCallingUidPersistentSystemProcess + : (realCallingUid == Process.SYSTEM_UID) + || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; if (realCallingUid != callingUid) { // don't abort if the realCallingUid is in the foreground and callingUid isn't if (isRealCallingUidForeground) { @@ -975,60 +989,14 @@ class ActivityStarter { + "; isBgStartWhitelisted: " + allowBackgroundActivityStart + "; intent: " + intent + "]"); - maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, - null /*r*/, originatingPendingIntent, true /*abortedStart*/); - return true; - } - - /** Returns true if uid is in a persistent state. */ - private boolean isUidPersistentSystemProcess(int uid) { - return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI); - } - - private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid, - Intent intent, WindowProcessController callerApp, ActivityRecord r, - PendingIntentRecord originatingPendingIntent, boolean abortedStart) { - boolean callerAppHasForegroundActivity = - callerApp != null && callerApp.hasForegroundActivities(); - if (!mService.isActivityStartsLoggingEnabled() || callerAppHasForegroundActivity - || (!abortedStart && r == null)) { - // skip logging in this case - return; - } - - try { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "logActivityStart"); - final int callingUidProcState = mService.getUidStateLocked(callingUid); - final boolean callingUidHasAnyVisibleWindow = - mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid); - final int realCallingUidProcState = (callingUid == realCallingUid) - ? callingUidProcState - : mService.getUidStateLocked(realCallingUid); - final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid) - ? callingUidHasAnyVisibleWindow - : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid( - realCallingUid); - final String targetPackage = (r != null) ? r.packageName : null; - final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1; - final int targetUidProcState = mService.getUidStateLocked(targetUid); - final boolean targetUidHasAnyVisibleWindow = (targetUid != -1) - ? mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(targetUid) - : false; - final String targetWhitelistTag = (targetUid != -1) - ? mService.getPendingTempWhitelistTagForUidLocked(targetUid) - : null; - - mSupervisor.getActivityMetricsLogger().logActivityStart(intent, callerApp, r, - callingUid, callingPackage, callingUidProcState, - callingUidHasAnyVisibleWindow, - realCallingUid, realCallingUidProcState, - realCallingUidHasAnyVisibleWindow, - targetUid, targetPackage, targetUidProcState, - targetUidHasAnyVisibleWindow, targetWhitelistTag, + // log aborted activity start to TRON + if (mService.isActivityStartsLoggingEnabled()) { + mSupervisor.getActivityMetricsLogger().logAbortedBgActivityStart(intent, callerApp, + callingUid, callingPackage, callingUidProcState, callingUidHasAnyVisibleWindow, + realCallingUid, realCallingUidProcState, realCallingUidHasAnyVisibleWindow, (originatingPendingIntent != null)); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } + return true; } /** @@ -1632,7 +1600,7 @@ class ActivityStarter { // Also, we don't want to resume activities in a task that currently has an overlay // as the starting activity just needs to be in the visible paused state until the // over is removed. - mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. mTargetStack.getDisplay().mDisplayContent.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java index 29645f68b2fb..ed029cd722cf 100644 --- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; + import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT; import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR; import static com.android.server.wm.AppWindowThumbnailProto.WIDTH; @@ -82,7 +85,8 @@ class AppWindowThumbnail implements Animatable { .setName("thumbnail anim: " + appToken.toString()) .setBufferSize(mWidth, mHeight) .setFormat(PixelFormat.TRANSLUCENT) - .setMetadata(appToken.windowType, + .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType) + .setMetadata(METADATA_OWNER_UID, window != null ? window.mOwnerUid : Binder.getCallingUid()) .build(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 40063326e76e..bbf115f17969 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2574,6 +2574,10 @@ public class DisplayPolicy { } } + void notifyDisplayReady() { + mHandler.post(() -> getStatusBarManagerInternal().onDisplayReady(getDisplayId())); + } + /** * Return the display width available after excluding any screen * decorations that could never be removed in Honeycomb. That is, system bar or diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e79820338c55..66666e681e7a 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -69,7 +69,7 @@ class InsetsSourceProvider { InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent) { - mClientVisible = InsetsState.getDefaultVisibly(source.getType()); + mClientVisible = InsetsState.getDefaultVisibility(source.getType()); mSource = source; mDisplayContent = displayContent; mStateController = stateController; @@ -153,6 +153,7 @@ class InsetsSourceProvider { return; } mAdapter = new ControlAdapter(); + setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter, !mClientVisible /* hidden */); mControllingWin = target; @@ -219,7 +220,7 @@ class InsetsSourceProvider { public void onAnimationCancelled(SurfaceControl animationLeash) { if (mAdapter == this) { mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this); - setClientVisible(InsetsState.getDefaultVisibly(mSource.getType())); + setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); mControl = null; mControllingWin = null; mAdapter = null; diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 9b7214120aed..624fdc2e168e 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -175,6 +175,12 @@ class RootActivityContainer extends ConfigurationContainer private ActivityDisplay mDefaultDisplay; private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>(); + /** + * Cached value of the topmost resumed activity in the system. Updated when new activity is + * resumed. + */ + private ActivityRecord mTopResumedActivity; + /** The current user */ int mCurrentUser; /** Stack id of the front stack when user switched, indexed by userId. */ @@ -1108,28 +1114,58 @@ class RootActivityContainer extends ConfigurationContainer return false; } + boolean result = false; if (targetStack != null && (targetStack.isTopStackOnDisplay() || getTopDisplayFocusedStack() == targetStack)) { - return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); + result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } - // Resume all top activities in focused stacks on all displays. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); - final ActivityStack focusedStack = display.getFocusedStack(); - if (focusedStack == null) { - continue; + for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); + if (!stack.isFocusableAndVisible() || topRunningActivity == null) { + continue; + } + if (topRunningActivity.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + stack.executeAppTransition(targetOptions); + } else { + resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); + } } - final ActivityRecord r = focusedStack.topRunningActivityLocked(); - if (r == null || !r.isState(RESUMED)) { - focusedStack.resumeTopActivityUncheckedLocked(null, null); - } else if (r.isState(RESUMED)) { - // Kick off any lingering app transitions form the MoveTaskToFront operation. - focusedStack.executeAppTransition(targetOptions); + if (!resumedOnDisplay) { + // In cases when there are no valid activities (e.g. device just booted or launcher + // crashed) it's possible that nothing was resumed on a display. Requesting resume + // of top activity in focused stack explicitly will make sure that at least home + // activity is started and resumed, and no recursion occurs. + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack != null) { + focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); + } } } - return false; + return result; + } + + void updateTopResumedActivityIfNeeded() { + final ActivityRecord prevTopActivity = mTopResumedActivity; + final ActivityStack topStack = getTopDisplayFocusedStack(); + if (topStack == null || topStack.mResumedActivity == prevTopActivity) { + return; + } + // Clear previous top state + if (prevTopActivity != null) { + prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */); + } + // Update the current top activity + mTopResumedActivity = topStack.mResumedActivity; + if (mTopResumedActivity != null) { + mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */); + } } void applySleepTokens(boolean applyToStacks) { @@ -1283,16 +1319,21 @@ class RootActivityContainer extends ConfigurationContainer public void onDisplayAdded(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId); synchronized (mService.mGlobalLock) { - getActivityDisplayOrCreate(displayId); + final ActivityDisplay display = getActivityDisplayOrCreate(displayId); // Do not start home before booting, or it may accidentally finish booting before it // starts. Instead, we expect home activities to be launched when the system is ready // (ActivityManagerService#systemReady). if (mService.isBooted() || mService.isBooting()) { - startHomeOnDisplay(mCurrentUser, "displayAdded", displayId); + startSystemDecorations(display.mDisplayContent); } } } + private void startSystemDecorations(final DisplayContent displayContent) { + startHomeOnDisplay(mCurrentUser, "displayAdded", displayContent.getDisplayId()); + displayContent.getDisplayPolicy().notifyDisplayReady(); + } + @Override public void onDisplayRemoved(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId); @@ -1386,6 +1427,7 @@ class RootActivityContainer extends ConfigurationContainer mActivityDisplays.remove(display); mActivityDisplays.add(position, display); } + updateTopResumedActivityIfNeeded(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a7dd55b8a160..476bd6e9abe9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; +import static android.view.SurfaceControl.METADATA_TASK_ID; import static com.android.server.EventLogTags.WM_TASK_REMOVED; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; @@ -643,6 +644,11 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta return getAppAnimationLayer(ANIMATION_LAYER_HOME); } + @Override + SurfaceControl.Builder makeSurface() { + return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId); + } + boolean isTaskAnimating() { final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController(); if (recentsAnim != null) { diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 69dcaf473b12..0529ed128130 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -698,6 +698,14 @@ class TaskRecord extends ConfigurationContainer { return false; } + final boolean toTopOfStack = position == MAX_VALUE; + if (toTopOfStack && toStack.getResumedActivity() != null + && toStack.topRunningActivityLocked() != null) { + // Pause the resumed activity on the target stack while re-parenting task on top of it. + toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, + null /* resuming */, false /* pauseImmediately */); + } + final int toStackWindowingMode = toStack.getWindowingMode(); final ActivityRecord topActivity = getTopActivity(); diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index c2a8e7efb5a5..dea3597989be 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -35,7 +37,6 @@ import android.os.IBinder; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowContentFrameStats; @@ -103,7 +104,8 @@ class WindowSurfaceController { .setBufferSize(w, h) .setFormat(format) .setFlags(flags) - .setMetadata(windowType, ownerUid); + .setMetadata(METADATA_WINDOW_TYPE, windowType) + .setMetadata(METADATA_OWNER_UID, ownerUid); mSurfaceControl = b.build(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index e99dd4f1cbae..bbecc6359a40 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -16,6 +16,9 @@ package com.android.server.net.ipmemorystore; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; @@ -27,7 +30,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQuery; -import android.net.NetworkUtils; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; import android.util.Log; @@ -200,7 +202,7 @@ public class IpMemoryStoreDatabase { if (null == attributes) return values; if (null != attributes.assignedV4Address) { values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, - NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + inet4AddressToIntHTH(attributes.assignedV4Address)); } if (null != attributes.groupHint) { values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); @@ -254,7 +256,7 @@ public class IpMemoryStoreDatabase { getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); if (0 != assignedV4AddressInt) { - builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt)); } builder.setGroupHint(groupHint); if (null != dnsAddressesBlob) { diff --git a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java index f068c3ac16e2..1fe2328f1cdb 100644 --- a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -16,7 +16,7 @@ package android.net.dhcp; -import static android.net.NetworkUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import android.annotation.NonNull; import android.net.LinkAddress; diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java deleted file mode 100644 index a61c2efd64da..000000000000 --- a/services/net/java/android/net/ip/IpClient.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * 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.ip; - -import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; - -import android.content.Context; -import android.net.LinkProperties; -import android.net.Network; -import android.net.ProxyInfo; -import android.net.StaticIpConfiguration; -import android.net.apf.ApfCapabilities; -import android.os.ConditionVariable; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated. - * @hide - */ -public class IpClient { - private static final String TAG = IpClient.class.getSimpleName(); - private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000; - - public static final String DUMP_ARG = "ipclient"; - - private final ConditionVariable mIpClientCv; - private final ConditionVariable mShutdownCv; - - private volatile IIpClient mIpClient; - - /** - * @see IpClientCallbacks - */ - public static class Callback extends IpClientCallbacks {} - - /** - * IpClient callback that allows clients to block until provisioning is complete. - */ - public static class WaitForProvisioningCallback extends Callback { - private final ConditionVariable mCV = new ConditionVariable(); - private LinkProperties mCallbackLinkProperties; - - /** - * Block until either {@link #onProvisioningSuccess(LinkProperties)} or - * {@link #onProvisioningFailure(LinkProperties)} is called. - */ - public LinkProperties waitForProvisioning() { - mCV.block(); - return mCallbackLinkProperties; - } - - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - mCallbackLinkProperties = newLp; - mCV.open(); - } - - @Override - public void onProvisioningFailure(LinkProperties newLp) { - mCallbackLinkProperties = null; - mCV.open(); - } - } - - private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy { - /** - * Create a new IpClientCallbacksProxy. - */ - CallbackImpl(IpClientCallbacks cb) { - super(cb); - } - - @Override - public void onIpClientCreated(IIpClient ipClient) { - mIpClient = ipClient; - mIpClientCv.open(); - super.onIpClientCreated(ipClient); - } - - @Override - public void onQuit() { - mShutdownCv.open(); - super.onQuit(); - } - } - - /** - * Create a new IpClient. - */ - public IpClient(Context context, String iface, Callback callback) { - mIpClientCv = new ConditionVariable(false); - mShutdownCv = new ConditionVariable(false); - - IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback)); - } - - /** - * @see IpClient#IpClient(Context, String, IpClient.Callback) - */ - public IpClient(Context context, String iface, Callback callback, - INetworkManagementService nms) { - this(context, iface, callback); - } - - private interface IpClientAction { - void useIpClient(IIpClient ipClient) throws RemoteException; - } - - private void doWithIpClient(IpClientAction action) { - mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - try { - action.useIpClient(mIpClient); - } catch (RemoteException e) { - Log.e(TAG, "Error communicating with IpClient", e); - } - } - - /** - * Notify IpClient that PreDhcpAction is completed. - */ - public void completedPreDhcpAction() { - doWithIpClient(c -> c.completedPreDhcpAction()); - } - - /** - * Confirm the provisioning configuration. - */ - public void confirmConfiguration() { - doWithIpClient(c -> c.confirmConfiguration()); - } - - /** - * Notify IpClient that packet filter read is complete. - */ - public void readPacketFilterComplete(byte[] data) { - doWithIpClient(c -> c.readPacketFilterComplete(data)); - } - - /** - * Shutdown the IpClient altogether. - */ - public void shutdown() { - doWithIpClient(c -> c.shutdown()); - } - - /** - * Start the IpClient provisioning. - */ - public void startProvisioning(ProvisioningConfiguration config) { - doWithIpClient(c -> c.startProvisioning(config.toStableParcelable())); - } - - /** - * Stop the IpClient. - */ - public void stop() { - doWithIpClient(c -> c.stop()); - } - - /** - * Set the IpClient TCP buffer sizes. - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes)); - } - - /** - * Set the IpClient HTTP proxy. - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo))); - } - - /** - * Set the IpClient multicast filter. - */ - public void setMulticastFilter(boolean enabled) { - doWithIpClient(c -> c.setMulticastFilter(enabled)); - } - - /** - * Dump IpClient logs. - */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args)); - } - - /** - * Block until IpClient shutdown. - */ - public void awaitShutdown() { - mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - } - - /** - * Create a new ProvisioningConfiguration. - */ - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - - /** - * TODO: remove after migrating clients to use the shared configuration class directly. - * @see android.net.shared.ProvisioningConfiguration - */ - public static class ProvisioningConfiguration - extends android.net.shared.ProvisioningConfiguration { - public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) { - super(other); - } - - /** - * @see android.net.shared.ProvisioningConfiguration.Builder - */ - public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder { - // Override all methods to have a return type matching this Builder - @Override - public Builder withoutIPv4() { - super.withoutIPv4(); - return this; - } - - @Override - public Builder withoutIPv6() { - super.withoutIPv6(); - return this; - } - - @Override - public Builder withoutMultinetworkPolicyTracker() { - super.withoutMultinetworkPolicyTracker(); - return this; - } - - @Override - public Builder withoutIpReachabilityMonitor() { - super.withoutIpReachabilityMonitor(); - return this; - } - - @Override - public Builder withPreDhcpAction() { - super.withPreDhcpAction(); - return this; - } - - @Override - public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - super.withPreDhcpAction(dhcpActionTimeoutMs); - return this; - } - - @Override - public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - super.withStaticConfiguration(staticConfig); - return this; - } - - @Override - public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - super.withApfCapabilities(apfCapabilities); - return this; - } - - @Override - public Builder withProvisioningTimeoutMs(int timeoutMs) { - super.withProvisioningTimeoutMs(timeoutMs); - return this; - } - - @Override - public Builder withRandomMacAddress() { - super.withRandomMacAddress(); - return this; - } - - @Override - public Builder withStableMacAddress() { - super.withStableMacAddress(); - return this; - } - - @Override - public Builder withNetwork(Network network) { - super.withNetwork(network); - return this; - } - - @Override - public Builder withDisplayName(String displayName) { - super.withDisplayName(displayName); - return this; - } - - @Override - public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); - } - } - } -} diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java index 2c368c81523e..00073503886a 100644 --- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java +++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java @@ -73,7 +73,7 @@ public final class IpConfigurationParcelableUtil { public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) { if (results == null) return null; final DhcpResultsParcelable p = new DhcpResultsParcelable(); - p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results); + p.baseConfiguration = toStableParcelable(results.toStaticIpConfiguration()); p.leaseDuration = results.leaseDuration; p.mtu = results.mtu; p.serverAddress = parcelAddress(results.serverAddress); diff --git a/services/net/java/android/net/shared/NetdService.java b/services/net/java/android/net/shared/NetdService.java index be0f5f2d4f34..f5ae72587294 100644 --- a/services/net/java/android/net/shared/NetdService.java +++ b/services/net/java/android/net/shared/NetdService.java @@ -16,6 +16,7 @@ package android.net.shared; +import android.content.Context; import android.net.INetd; import android.os.RemoteException; import android.os.ServiceManager; @@ -28,7 +29,6 @@ import android.util.Log; */ public class NetdService { private static final String TAG = NetdService.class.getSimpleName(); - private static final String NETD_SERVICE_NAME = "netd"; private static final long BASE_TIMEOUT_MS = 100; private static final long MAX_TIMEOUT_MS = 1000; @@ -48,7 +48,7 @@ public class NetdService { // NOTE: ServiceManager does no caching for the netd service, // because netd is not one of the defined common services. final INetd netdInstance = INetd.Stub.asInterface( - ServiceManager.getService(NETD_SERVICE_NAME)); + ServiceManager.getService(Context.NETD_SERVICE)); if (netdInstance == null) { Log.w(TAG, "WARNING: returning null INetd instance."); } diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java new file mode 100644 index 000000000000..9f1cbcd7ec27 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.attention.AttentionManagerInternal; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class AttentionDetectorTest extends AndroidTestCase { + + private @Mock AttentionManagerInternal mAttentionManagerInternal; + private @Mock Runnable mOnUserAttention; + private TestableAttentionDetector mAttentionDetector; + private long mAttentionTimeout; + private long mNextDimming; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any())) + .thenReturn(true); + mAttentionDetector = new TestableAttentionDetector(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE); + mAttentionDetector.setAttentionServiceSupported(true); + mNextDimming = SystemClock.uptimeMillis() + 3000L; + } + + @Test + public void testOnUserActivity_checksAttention() { + long when = registerAttention(); + verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnUserActivity_doesntCheckIfNotSupported() { + mAttentionDetector.setAttentionServiceSupported(false); + long when = registerAttention(); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(mNextDimming).isEqualTo(when); + } + + @Test + public void onUserActivity_ignoresWhiteListedActivityTypes() { + for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) { + int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i); + if (result == -1) { + throw new AssertionError("User activity " + i + " isn't listed in" + + " AttentionDetector#onUserActivity. Please consider how this new activity" + + " type affects the attention service."); + } + } + } + + @Test + public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() { + long now = SystemClock.uptimeMillis(); + mNextDimming = now; + mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(mNextDimming + 5000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_ignoresAfterMaximumExtension() { + long now = SystemClock.uptimeMillis(); + mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(now + 2000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_skipsIfAlreadyScheduled() { + registerAttention(); + reset(mAttentionManagerInternal); + long when = mAttentionDetector.updateUserActivity(mNextDimming); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() { + registerAttention(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP); + verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt()); + } + + @Test + public void testCallbackOnSuccess_ignoresIfNoAttention() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + @Test + public void testCallbackOnSuccess_callsCallback() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention).run(); + } + + @Test + public void testCallbackOnFailure_unregistersCurrentRequestCode() { + registerAttention(); + mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_FAILURE_UNKNOWN); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + private long registerAttention() { + mAttentionTimeout = 4000L; + mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH); + return mAttentionDetector.updateUserActivity(mNextDimming); + } + + private class TestableAttentionDetector extends AttentionDetector { + + private boolean mAttentionServiceSupported; + + TestableAttentionDetector() { + super(AttentionDetectorTest.this.mOnUserAttention, new Object()); + mAttentionManager = mAttentionManagerInternal; + mMaximumExtensionMillis = 10000L; + } + + void setAttentionServiceSupported(boolean supported) { + mAttentionServiceSupported = supported; + } + + @Override + public boolean isAttentionServiceSupported() { + return mAttentionServiceSupported; + } + + @Override + public long getAttentionTimeout() { + return mAttentionTimeout; + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java index 410ab8732a08..e658d179f4f3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java @@ -59,7 +59,7 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase { signals.putStringArrayList(Adjustment.KEY_PEOPLE, people); ArrayList<Notification.Action> smartActions = new ArrayList<>(); smartActions.add(createAction()); - signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions); + signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, smartActions); Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); r.addAdjustment(adjustment); 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 62229235a026..9c6ab0ab9aa9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3410,6 +3410,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); + assertEquals(1, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(1, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + } + + @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture()); + assertEquals(2, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(2, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName()); + } + + @Test + public void testNoNotificationsHiddenOnDistractingPackageBroadcast() { + // post notification from this package + final NotificationRecord notif1 = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + mService.addNotification(notif1); + + // on broadcast, nothing is hidden since no notifications are of package "test_package" + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"}); + ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); + assertEquals(0, captor.getValue().size()); + } + + @Test public void testCanUseManagedServicesLowRamNoWatchNullPkg() { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mActivityManager.isLowRamDevice()).thenReturn(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8be63fc43adb..319ffed3778c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -31,6 +31,7 @@ import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; +import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -75,6 +76,9 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); mTask = mStack.getChildAt(0); mActivity = mTask.getTopActivity(); + + doReturn(false).when(mService).isBooting(); + doReturn(true).when(mService).isBooted(); } @Test @@ -117,22 +121,23 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - // The activity is in the focused stack so it should not move to paused. + // The activity is in the focused stack so it should be resumed. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(STOPPED)); + assertTrue(mActivity.isState(RESUMED)); assertFalse(pauseFound.value); - // Clear focused stack - final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); - when(display.getFocusedStack()).thenReturn(null); + // Make the activity non focusable + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); + doReturn(false).when(mActivity).isFocusable(); - // In the unfocused stack, the activity should move to paused. + // If the activity is not focusable, it should move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); + doReturn(true).when(mActivity).isFocusable(); mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); 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 056568a0de64..ace965b8b42c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -523,9 +523,8 @@ public class ActivityStarterTests extends ActivityTestsBase { starter.setReason("testActivityStartsLogging_noLoggingWhenDisabled").execute(); // verify logging wasn't done - verify(mActivityMetricsLogger, never()).logActivityStart(any(), any(), any(), anyInt(), - any(), anyInt(), anyBoolean(), anyInt(), anyInt(), anyBoolean(), anyInt(), any(), - anyInt(), anyBoolean(), any(), anyBoolean()); + verify(mActivityMetricsLogger, never()).logAbortedBgActivityStart(any(), any(), anyInt(), + any(), anyInt(), anyBoolean(), anyInt(), anyInt(), anyBoolean(), anyBoolean()); } /** @@ -546,10 +545,9 @@ public class ActivityStarterTests extends ActivityTestsBase { starter.setReason("testActivityStartsLogging_logsWhenEnabled").execute(); // verify the above activity start was logged - verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(), + verify(mActivityMetricsLogger, times(1)).logAbortedBgActivityStart(any(), any(), eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(), - eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(), - any(), anyInt(), anyBoolean(), any(), eq(false)); + eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), eq(false)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 68df87e3e27d..ea8f33f0c630 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -55,6 +55,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; +import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; @@ -425,6 +426,7 @@ class ActivityTestsBase { doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); // allow background activity starts by default doReturn(true).when(this).isBackgroundActivityStartsEnabled(); + doNothing().when(this).updateCpuStats(); } void setup(IntentFirewall intentFirewall, PendingIntentController intentController, @@ -580,6 +582,8 @@ class ActivityTestsBase { doNothing().when(this).acquireLaunchWakelock(); doReturn(mKeyguardController).when(this).getKeyguardController(); + mLaunchingActivity = mock(PowerManager.WakeLock.class); + initialize(); } diff --git a/telephony/java/android/telephony/IFinancialSmsCallback.aidl b/telephony/java/android/telephony/IFinancialSmsCallback.aidl new file mode 100644 index 000000000000..aa88615c15cf --- /dev/null +++ b/telephony/java/android/telephony/IFinancialSmsCallback.aidl @@ -0,0 +1,34 @@ +/* +** 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.telephony; + +import android.app.PendingIntent; +import android.database.CursorWindow; +import android.net.Uri; +import android.os.Bundle; +import com.android.internal.telephony.SmsRawData; + +/** Interface for returning back the financial sms messages asynchrously. + * @hide + */ +interface IFinancialSmsCallback { + /** + * Return sms messages back to calling financial app. + * + * @param messages the sms messages returned for cinancial app. + */ + oneway void onGetSmsMessagesForFinancialApp(in CursorWindow messages); +} diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index bf9bf9a1ba8f..44756307a4b3 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1631,8 +1631,9 @@ public class ServiceState implements Parcelable { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static boolean bearerBitmapHasCdma(int radioTechnologyBitmap) { - return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK & radioTechnologyBitmap) != 0; + public static boolean bearerBitmapHasCdma(int networkTypeBitmask) { + return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK + & convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask)) != 0; } /** @hide */ diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index d777bf123b67..fae7920d1ab8 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -16,6 +16,9 @@ package android.telephony; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; @@ -30,8 +33,10 @@ import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.database.CursorWindow; import android.net.Uri; import android.os.BaseBundle; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; @@ -49,6 +54,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /* * TODO(code review): Curious question... Why are a lot of these @@ -2144,6 +2150,116 @@ public final class SmsManager { } } + /** callback for providing asynchronous sms messages for financial app. */ + public abstract static class FinancialSmsCallback { + /** + * Callback to send sms messages back to financial app asynchronously. + * + * @param msgs SMS messages. + */ + public abstract void onFinancialSmsMessages(CursorWindow msgs); + }; + + /** + * Get SMS messages for the calling financial app. + * The result will be delivered asynchronously in the passing in callback interface. + * + * @param params the parameters to filter SMS messages returned. + * @param executor the executor on which callback will be invoked. + * @param callback a callback to receive CursorWindow with SMS messages. + */ + @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) + public void getSmsMessagesForFinancialApp( + Bundle params, + @NonNull @CallbackExecutor Executor executor, + @NonNull FinancialSmsCallback callback) { + try { + ISms iccSms = getISmsServiceOrThrow(); + iccSms.getSmsMessagesForFinancialApp( + getSubscriptionId(), ActivityThread.currentPackageName(), params, + new IFinancialSmsCallback.Stub() { + public void onGetSmsMessagesForFinancialApp(CursorWindow msgs) { + Binder.withCleanCallingIdentity(() -> executor.execute( + () -> callback.onFinancialSmsMessages(msgs))); + }}); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The prefixes is a list of prefix {@code String} separated by this delimiter. + * @hide + */ + public static final String REGEX_PREFIX_DELIMITER = ","; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The success status to be added into the intent to be sent to the calling package. + * @hide + */ + public static final int RESULT_STATUS_SUCCESS = 0; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * The timeout status to be added into the intent to be sent to the calling package. + * @hide + */ + public static final int RESULT_STATUS_TIMEOUT = 1; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * Intent extra key of the retrieved SMS message as a {@code String}. + * @hide + */ + public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE"; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * Intent extra key of SMS retriever status, which indicates whether the request for the + * coming SMS message is SUCCESS or TIMEOUT + * @hide + */ + public static final String EXTRA_STATUS = "android.telephony.extra.STATUS"; + /** + * @see #createAppSpecificSmsTokenWithPackageInfo(). + * [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int} + * @hide + */ + public static final String EXTRA_SIM_SUBSCRIPTION_ID = + "android.telephony.extra.SIM_SUBSCRIPTION_ID"; + + /** + * Create a single use app specific incoming SMS request for the calling package. + * + * This method returns a token that if included in a subsequent incoming SMS message, and the + * SMS message has a prefix from the given prefixes list, the provided {@code intent} will be + * sent with the SMS data to the calling package. + * + * The token is only good for one use within a reasonable amount of time. After an SMS has been + * received containing the token all subsequent SMS messages with the token will be routed as + * normal. + * + * An app can only have one request at a time, if the app already has a request pending it will + * be replaced with a new request. + * + * @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The + * matching SMS message should have at least one of the prefixes in the beginning of the + * message. + * @param intent this intent is sent when the matching SMS message is received. + * @return Token to include in an SMS message. + */ + @Nullable + public String createAppSpecificSmsTokenWithPackageInfo( + @Nullable String prefixes, @NonNull PendingIntent intent) { + try { + ISms iccSms = getISmsServiceOrThrow(); + return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(), + ActivityThread.currentPackageName(), prefixes, intent); + + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + return null; + } + } + /** * Filters a bundle to only contain MMS config variables. * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index e710e0e02c46..80ee0a72799e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6848,13 +6848,13 @@ public class TelephonyManager { /** * Values used to return status for hasCarrierPrivileges call. */ - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; - /** @hide */ @SystemApi + /** @hide */ @SystemApi @TestApi public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; /** @@ -7056,6 +7056,7 @@ public class TelephonyManager { /** @hide */ @SystemApi + @TestApi @SuppressLint("Doclava125") public int checkCarrierPrivilegesForPackage(String pkgName) { try { @@ -10133,4 +10134,26 @@ public class TelephonyManager { } return ret; } + + /** + * Broadcast intent action for network country code changes. + * + * <p> + * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current + * network returned by {@link #getNetworkCountryIso()}. + * + * @see #EXTRA_NETWORK_COUNTRY + * @see #getNetworkCountryIso() + */ + public static final String ACTION_NETWORK_COUNTRY_CHANGED = + "android.telephony.action.NETWORK_COUNTRY_CHANGED"; + + /** + * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the + * the country code in ISO 3166 format. + * <p class="note"> + * Retrieve with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_NETWORK_COUNTRY = + "android.telephony.extra.NETWORK_COUNTRY"; } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 294c79ba57a2..3d2fe5fec14a 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.net.LinkAddress; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.data.ApnSetting.ProtocolType; import java.net.InetAddress; import java.util.ArrayList; @@ -40,7 +41,7 @@ public final class DataCallResponse implements Parcelable { private final int mSuggestedRetryTime; private final int mCid; private final int mActive; - private final String mType; + private final int mProtocolType; private final String mIfname; private final List<LinkAddress> mAddresses; private final List<InetAddress> mDnses; @@ -53,8 +54,8 @@ public final class DataCallResponse implements Parcelable { * @param suggestedRetryTime The suggested data retry time in milliseconds. * @param cid The unique id of the data connection. * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active. - * @param type The connection protocol, should be one of the PDP_type values in TS 27.007 - * section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @param protocolType The connection protocol, should be one of the PDP_type values in 3GPP + * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". * @param ifname The network interface name. * @param addresses A list of addresses with optional "/" prefix length, e.g., * "192.0.1.3" or "192.0.1.11/16 2001:db8::1/64". Typically 1 IPv4 or 1 IPv6 or @@ -71,7 +72,7 @@ public final class DataCallResponse implements Parcelable { * either not sent a value or sent an invalid value. */ public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, - @Nullable String type, @Nullable String ifname, + @ProtocolType int protocolType, @Nullable String ifname, @Nullable List<LinkAddress> addresses, @Nullable List<InetAddress> dnses, @Nullable List<InetAddress> gateways, @@ -80,7 +81,7 @@ public final class DataCallResponse implements Parcelable { mSuggestedRetryTime = suggestedRetryTime; mCid = cid; mActive = active; - mType = (type == null) ? "" : type; + mProtocolType = protocolType; mIfname = (ifname == null) ? "" : ifname; mAddresses = (addresses == null) ? new ArrayList<>() : addresses; mDnses = (dnses == null) ? new ArrayList<>() : dnses; @@ -94,7 +95,7 @@ public final class DataCallResponse implements Parcelable { mSuggestedRetryTime = source.readInt(); mCid = source.readInt(); mActive = source.readInt(); - mType = source.readString(); + mProtocolType = source.readInt(); mIfname = source.readString(); mAddresses = new ArrayList<>(); source.readList(mAddresses, LinkAddress.class.getClassLoader()); @@ -128,11 +129,10 @@ public final class DataCallResponse implements Parcelable { public int getActive() { return mActive; } /** - * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section - * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol type. */ - @NonNull - public String getType() { return mType; } + @ProtocolType + public int getProtocolType() { return mProtocolType; } /** * @return The network interface name. @@ -181,7 +181,7 @@ public final class DataCallResponse implements Parcelable { .append(" retry=").append(mSuggestedRetryTime) .append(" cid=").append(mCid) .append(" active=").append(mActive) - .append(" type=").append(mType) + .append(" protocolType=").append(mProtocolType) .append(" ifname=").append(mIfname) .append(" addresses=").append(mAddresses) .append(" dnses=").append(mDnses) @@ -205,7 +205,7 @@ public final class DataCallResponse implements Parcelable { && this.mSuggestedRetryTime == other.mSuggestedRetryTime && this.mCid == other.mCid && this.mActive == other.mActive - && this.mType.equals(other.mType) + && this.mProtocolType == other.mProtocolType && this.mIfname.equals(other.mIfname) && mAddresses.size() == other.mAddresses.size() && mAddresses.containsAll(other.mAddresses) @@ -220,8 +220,8 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { - return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses, - mDnses, mGateways, mPcscfs, mMtu); + return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mProtocolType, mIfname, + mAddresses, mDnses, mGateways, mPcscfs, mMtu); } @Override @@ -235,7 +235,7 @@ public final class DataCallResponse implements Parcelable { dest.writeInt(mSuggestedRetryTime); dest.writeInt(mCid); dest.writeInt(mActive); - dest.writeString(mType); + dest.writeInt(mProtocolType); dest.writeString(mIfname); dest.writeList(mAddresses); dest.writeList(mDnses); diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index da4822cc1d14..1d196f9b2885 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -16,14 +16,23 @@ package android.telephony.data; +import static android.telephony.data.ApnSetting.ProtocolType; + +import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.TelephonyManager.NetworkTypeBitMask; +import android.telephony.data.ApnSetting.ApnType; +import android.telephony.data.ApnSetting.AuthType; import android.text.TextUtils; import com.android.internal.telephony.RILConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Description of a mobile data profile used for establishing * data connections. @@ -32,24 +41,39 @@ import com.android.internal.telephony.RILConstants; */ @SystemApi public final class DataProfile implements Parcelable { - - // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network. + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"TYPE_"}, + value = { + TYPE_COMMON, + TYPE_3GPP, + TYPE_3GPP2}) + public @interface DataProfileType {} + + /** Common data profile */ public static final int TYPE_COMMON = 0; + + /** 3GPP type data profile */ public static final int TYPE_3GPP = 1; + + /** 3GPP2 type data profile */ public static final int TYPE_3GPP2 = 2; private final int mProfileId; private final String mApn; - private final String mProtocol; + @ProtocolType + private final int mProtocolType; + @AuthType private final int mAuthType; private final String mUserName; private final String mPassword; + @DataProfileType private final int mType; private final int mMaxConnsTime; @@ -60,10 +84,13 @@ public final class DataProfile implements Parcelable { private final boolean mEnabled; + @ApnType private final int mSupportedApnTypesBitmap; - private final String mRoamingProtocol; + @ProtocolType + private final int mRoamingProtocolType; + @NetworkTypeBitMask private final int mBearerBitmap; private final int mMtu; @@ -73,14 +100,14 @@ public final class DataProfile implements Parcelable { private final boolean mPreferred; /** @hide */ - public DataProfile(int profileId, String apn, String protocol, int authType, String userName, - String password, int type, int maxConnsTime, int maxConns, int waitTime, - boolean enabled, int supportedApnTypesBitmap, String roamingProtocol, - int bearerBitmap, int mtu, boolean persistent, boolean preferred) { - + public DataProfile(int profileId, String apn, @ProtocolType int protocolType, int authType, + String userName, String password, int type, int maxConnsTime, int maxConns, + int waitTime, boolean enabled, @ApnType int supportedApnTypesBitmap, + @ProtocolType int roamingProtocolType, @NetworkTypeBitMask int bearerBitmap, + int mtu, boolean persistent, boolean preferred) { this.mProfileId = profileId; this.mApn = apn; - this.mProtocol = protocol; + this.mProtocolType = protocolType; if (authType == -1) { authType = TextUtils.isEmpty(userName) ? RILConstants.SETUP_DATA_AUTH_NONE : RILConstants.SETUP_DATA_AUTH_PAP_CHAP; @@ -95,7 +122,7 @@ public final class DataProfile implements Parcelable { this.mEnabled = enabled; this.mSupportedApnTypesBitmap = supportedApnTypesBitmap; - this.mRoamingProtocol = roamingProtocol; + this.mRoamingProtocolType = roamingProtocolType; this.mBearerBitmap = bearerBitmap; this.mMtu = mtu; this.mPersistent = persistent; @@ -106,7 +133,7 @@ public final class DataProfile implements Parcelable { public DataProfile(Parcel source) { mProfileId = source.readInt(); mApn = source.readString(); - mProtocol = source.readString(); + mProtocolType = source.readInt(); mAuthType = source.readInt(); mUserName = source.readString(); mPassword = source.readString(); @@ -116,7 +143,7 @@ public final class DataProfile implements Parcelable { mWaitTime = source.readInt(); mEnabled = source.readBoolean(); mSupportedApnTypesBitmap = source.readInt(); - mRoamingProtocol = source.readString(); + mRoamingProtocolType = source.readInt(); mBearerBitmap = source.readInt(); mMtu = source.readInt(); mPersistent = source.readBoolean(); @@ -134,16 +161,14 @@ public final class DataProfile implements Parcelable { public String getApn() { return mApn; } /** - * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section - * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol defined in 3GPP TS 27.007 section 10.1.1. */ - public String getProtocol() { return mProtocol; } + public @ProtocolType int getProtocol() { return mProtocolType; } /** - * @return The authentication protocol used for this PDP context - * (None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3) + * @return The authentication protocol used for this PDP context. */ - public int getAuthType() { return mAuthType; } + public @AuthType int getAuthType() { return mAuthType; } /** * @return The username for APN. Can be null. @@ -156,9 +181,9 @@ public final class DataProfile implements Parcelable { public String getPassword() { return mPassword; } /** - * @return The profile type. Could be one of TYPE_COMMON, TYPE_3GPP, or TYPE_3GPP2. + * @return The profile type. */ - public int getType() { return mType; } + public @DataProfileType int getType() { return mType; } /** * @return The period in seconds to limit the maximum connections. @@ -183,20 +208,19 @@ public final class DataProfile implements Parcelable { public boolean isEnabled() { return mEnabled; } /** - * @return The supported APN types bitmap. See RIL_ApnTypes for the value of each bit. + * @return The supported APN types bitmap. */ - public int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; } + public @ApnType int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; } /** - * @return The connection protocol on roaming network, should be one of the PDP_type values in - * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". + * @return The connection protocol on roaming network defined in 3GPP TS 27.007 section 10.1.1. */ - public String getRoamingProtocol() { return mRoamingProtocol; } + public @ProtocolType int getRoamingProtocol() { return mRoamingProtocolType; } /** - * @return The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit. + * @return The bearer bitmap indicating the applicable networks for this data profile. */ - public int getBearerBitmap() { return mBearerBitmap; } + public @NetworkTypeBitMask int getBearerBitmap() { return mBearerBitmap; } /** * @return The maximum transmission unit (MTU) size in bytes. @@ -222,12 +246,12 @@ public final class DataProfile implements Parcelable { @Override public String toString() { - return "DataProfile=" + mProfileId + "/" + mProtocol + "/" + mAuthType + return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType + "/" + (Build.IS_USER ? "***/***/***" : (mApn + "/" + mUserName + "/" + mPassword)) + "/" + mType + "/" + mMaxConnsTime + "/" + mMaxConns + "/" + mWaitTime + "/" + mEnabled + "/" + mSupportedApnTypesBitmap + "/" - + mRoamingProtocol + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/" + + mRoamingProtocolType + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/" + mPreferred; } @@ -242,7 +266,7 @@ public final class DataProfile implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mProfileId); dest.writeString(mApn); - dest.writeString(mProtocol); + dest.writeInt(mProtocolType); dest.writeInt(mAuthType); dest.writeString(mUserName); dest.writeString(mPassword); @@ -252,7 +276,7 @@ public final class DataProfile implements Parcelable { dest.writeInt(mWaitTime); dest.writeBoolean(mEnabled); dest.writeInt(mSupportedApnTypesBitmap); - dest.writeString(mRoamingProtocol); + dest.writeInt(mRoamingProtocolType); dest.writeInt(mBearerBitmap); dest.writeInt(mMtu); dest.writeBoolean(mPersistent); diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java new file mode 100644 index 000000000000..ac4d17a0ce65 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsException.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class defines an IMS-related exception that has been thrown while interacting with a + * device or carrier provided ImsService implementation. + * @hide + */ +@SystemApi +public class ImsException extends Exception { + + /** + * The operation has failed due to an unknown or unspecified error. + */ + public static final int CODE_ERROR_UNSPECIFIED = 0; + /** + * The operation has failed because there is no {@link ImsService} available to service it. This + * may be due to an {@link ImsService} crash or other illegal state. + * <p> + * This is a temporary error and the operation may be retried until the connection to the + * {@link ImsService} is restored. + */ + public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; + + /** + * This device or carrier configuration does not support IMS for this subscription. + * <p> + * This is a permanent configuration error and there should be no retry. + */ + public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CODE_ERROR_", value = { + CODE_ERROR_UNSPECIFIED, + CODE_ERROR_SERVICE_UNAVAILABLE, + CODE_ERROR_UNSUPPORTED_OPERATION + }) + public @interface ImsErrorCode {} + + private int mCode = CODE_ERROR_UNSPECIFIED; + + /** + * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message) { + super(getMessage(message, CODE_ERROR_UNSPECIFIED)); + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code) { + super(getMessage(message, code)); + mCode = code; + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a + * {@link Throwable} that contains the original error that was thrown to lead to this Exception. + * @param message an optional message to detail the error condition more specifically. + * @param cause the {@link Throwable} that caused this {@link ImsException} to be created. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) { + super(getMessage(message, code), cause); + mCode = code; + } + + /** + * @return the IMS Error code that is associated with this {@link ImsException}. + */ + public @ImsErrorCode int getCode() { + return mCode; + } + + private static String getMessage(String message, int code) { + StringBuilder builder; + if (!TextUtils.isEmpty(message)) { + builder = new StringBuilder(message); + builder.append(" (code: "); + builder.append(code); + builder.append(")"); + return builder.toString(); + } else { + return "code: " + code; + } + } +} diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 5b2e635b179f..eb99d5dcaaeb 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; * registration and MmTel capability status callbacks, as well as query/modify user settings for the * associated subscription. * - * @see #createForSubscriptionId(Context, int) + * @see #createForSubscriptionId(int) * @hide */ @SystemApi @@ -315,15 +315,12 @@ public class ImsMmTelManager { /** * Create an instance of ImsManager for the subscription id specified. * - * @param context The context to create this ImsMmTelManager instance within. * @param subId The ID of the subscription that this ImsMmTelManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ImsMmTelManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ImsMmTelManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -331,7 +328,7 @@ public class ImsMmTelManager { } /** - * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead. + * Only visible for testing, use {@link #createForSubscriptionId(int)} instead. * @hide */ @VisibleForTesting @@ -341,7 +338,7 @@ public class ImsMmTelManager { /** * Registers a {@link RegistrationCallback} with the system, which will provide registration - * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use + * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up. * @@ -354,13 +351,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@CallbackExecutor Executor executor, - @NonNull RegistrationCallback c) { + @NonNull RegistrationCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -372,6 +370,8 @@ public class ImsMmTelManager { getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -403,7 +403,7 @@ public class ImsMmTelManager { /** * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service * availability updates for the subscription specified in - * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)} + * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)} * can also be used to query this information at any time. * * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to @@ -419,13 +419,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull CapabilityCallback c) { + @NonNull CapabilityCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -437,6 +438,8 @@ public class ImsMmTelManager { getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -796,14 +799,6 @@ public class ImsMmTelManager { } } - private static SubscriptionManager getSubscriptionManager(Context context) { - SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); - if (manager == null) { - throw new RuntimeException("Could not find SubscriptionManager."); - } - return manager; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( ServiceManager.getService(Context.TELEPHONY_SERVICE)); diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 086a76546b2d..b171f7940944 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -172,15 +172,13 @@ public class ProvisioningManager { /** * Create a new {@link ProvisioningManager} for the subscription specified. - * @param context The context that this manager will use. + * * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ProvisioningManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ProvisioningManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -202,18 +200,21 @@ public class ProvisioningManager { * @see SubscriptionManager.OnSubscriptionsChangedListener * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or the subscription is invalid. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor, - @NonNull Callback callback) { + @NonNull Callback callback) throws ImsException { callback.setExecutor(executor); try { getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -332,6 +333,7 @@ public class ProvisioningManager { * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @@ -358,6 +360,7 @@ public class ProvisioningManager { * provisioning, false if the capability does require provisioning and has not been * provisioned yet. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index f35e88672a23..fea763ed5785 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -21,8 +21,10 @@ import android.telephony.ims.ImsReasonInfo; /** * This class defines a general IMS-related exception. * + * @deprecated Use {@link android.telephony.ims.ImsException} instead. * @hide */ +@Deprecated public class ImsException extends Exception { /** diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index a4eb424ab66e..b51eda3f1b34 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -18,6 +18,8 @@ package com.android.internal.telephony; import android.app.PendingIntent; import android.net.Uri; +import android.os.Bundle; +import android.telephony.IFinancialSmsCallback; import com.android.internal.telephony.SmsRawData; /** Interface for applications to access the ICC phone book. @@ -560,4 +562,30 @@ interface ISms { * @param intent PendingIntent to be sent when an SMS is received containing the token. */ String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent); + + /** + * Create an app-only incoming SMS request for the calling package. + * + * If an incoming text contains the token returned by this method the provided + * <code>PendingIntent</code> will be sent containing the SMS data. + * + * @param subId the SIM id. + * @param callingPkg the package name of the calling app. + * @param prefixes the caller provided prefixes + * @param intent PendingIntent to be sent when a SMS is received containing the token and one + * of the prefixes + */ + String createAppSpecificSmsTokenWithPackageInfo( + int subId, String callingPkg, String prefixes, in PendingIntent intent); + + /** + * Get sms inbox messages for the calling financial app. + * + * @param subId the SIM id. + * @param callingPkg the package name of the calling app. + * @param params parameters to filter the sms messages. + * @param callback the callback interface to deliver the result. + */ + void getSmsMessagesForFinancialApp( + int subId, String callingPkg, in Bundle params, in IFinancialSmsCallback callback); } diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java index 1cdf44d897b2..12c5c308739a 100644 --- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java +++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java @@ -18,6 +18,8 @@ package com.android.internal.telephony; import android.app.PendingIntent; import android.net.Uri; +import android.os.Bundle; +import android.telephony.IFinancialSmsCallback; import java.util.List; @@ -188,4 +190,16 @@ public class ISmsImplBase extends ISms.Stub { public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) { throw new UnsupportedOperationException(); } + + @Override + public String createAppSpecificSmsTokenWithPackageInfo( + int subId, String callingPkg, String prefixes, PendingIntent intent) { + throw new UnsupportedOperationException(); + } + + @Override + public void getSmsMessagesForFinancialApp( + int subId, String callingPkg, Bundle params, IFinancialSmsCallback callback) { + throw new UnsupportedOperationException(); + } } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 930003462110..d9b206f49dd5 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -268,31 +268,11 @@ public interface RILConstants { int LTE_ON_CDMA_FALSE = 0; int LTE_ON_CDMA_TRUE = 1; - int CDM_TTY_MODE_DISABLED = 0; - int CDM_TTY_MODE_ENABLED = 1; - - int CDM_TTY_FULL_MODE = 1; - int CDM_TTY_HCO_MODE = 2; - int CDM_TTY_VCO_MODE = 3; - - /* Setup a packet data connection. See ril.h RIL_REQUEST_SETUP_DATA_CALL */ - int SETUP_DATA_TECH_CDMA = 0; - int SETUP_DATA_TECH_GSM = 1; - int SETUP_DATA_AUTH_NONE = 0; int SETUP_DATA_AUTH_PAP = 1; int SETUP_DATA_AUTH_CHAP = 2; int SETUP_DATA_AUTH_PAP_CHAP = 3; - String SETUP_DATA_PROTOCOL_IP = "IP"; - String SETUP_DATA_PROTOCOL_IPV6 = "IPV6"; - String SETUP_DATA_PROTOCOL_IPV4V6 = "IPV4V6"; - - /* NV config radio reset types. */ - int NV_CONFIG_RELOAD_RESET = 1; - int NV_CONFIG_ERASE_RESET = 2; - int NV_CONFIG_FACTORY_RESET = 3; - /* LCE service related constants. */ int LCE_NOT_AVAILABLE = -1; int LCE_STOPPED = 0; diff --git a/test-mock/src/android/test/mock/MockCursor.java b/test-mock/src/android/test/mock/MockCursor.java index 576f24ad6384..f69db2c84111 100644 --- a/test-mock/src/android/test/mock/MockCursor.java +++ b/test-mock/src/android/test/mock/MockCursor.java @@ -24,6 +24,8 @@ import android.database.DataSetObserver; import android.net.Uri; import android.os.Bundle; +import java.util.List; + /** * A mock {@link android.database.Cursor} class that isolates the test code from real * Cursor implementation. @@ -226,11 +228,21 @@ public class MockCursor implements Cursor { } @Override + public void setNotificationUris(ContentResolver cr, List<Uri> uris) { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override public Uri getNotificationUri() { throw new UnsupportedOperationException("unimplemented mock method"); } @Override + public List<Uri> getNotificationUris() { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + @Override public void unregisterContentObserver(ContentObserver observer) { throw new UnsupportedOperationException("unimplemented mock method"); } diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index b381cbfbd0a9..0b3bd70ba5c9 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -79,6 +79,7 @@ android:singleUser="true" android:exported="true" /> <receiver android:name="TrackTimeReceiver" /> <receiver android:name="AlarmSpamReceiver" /> + <receiver android:name="SlowReceiver" /> <activity android:name="DisableScreenshotsActivity" android:label="DisableScreenshots" android:theme="@style/DisableScreenshots"> diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 0f4960887a33..2581e083cc64 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -16,21 +16,21 @@ package com.google.android.test.activity; -import java.util.ArrayList; -import java.util.List; - import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProviderClient; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; @@ -42,21 +42,18 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.graphics.Bitmap; import android.provider.Settings; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.ScrollView; -import android.widget.Toast; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.content.Context; -import android.content.pm.UserInfo; -import android.content.res.Configuration; -import android.util.Log; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; +import java.util.ArrayList; +import java.util.List; public class ActivityTestMain extends Activity { static final String TAG = "ActivityTest"; @@ -73,8 +70,13 @@ public class ActivityTestMain extends Activity { ServiceConnection mIsolatedConnection; + static final String SLOW_RECEIVER_ACTION = "com.google.android.test.activity.SLOW_ACTION"; + static final String SLOW_RECEIVER_EXTRA = "slow_ordinal"; + static final int MSG_SPAM = 1; static final int MSG_SPAM_ALARM = 2; + static final int MSG_SLOW_RECEIVER = 3; + static final int MSG_SLOW_ALARM_RECEIVER = 4; final Handler mHandler = new Handler() { @Override @@ -100,11 +102,58 @@ public class ActivityTestMain extends Activity { mAlarm.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, when+(30*1000), pi); scheduleSpamAlarm(30*1000); } break; + case MSG_SLOW_RECEIVER: { + // Several back to back, to illustrate dispatch policy + Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class); + intent.setAction(SLOW_RECEIVER_ACTION); + intent.putExtra(SLOW_RECEIVER_EXTRA, 1); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 2); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 3); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + } break; + case MSG_SLOW_ALARM_RECEIVER: { + // Several back to back, to illustrate dispatch policy + Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class); + intent.setAction(SLOW_RECEIVER_ACTION); + intent.putExtra(SLOW_RECEIVER_EXTRA, 1); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 2); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 3); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + + // Also send a broadcast alarm to evaluate the alarm fast-forward policy + intent.putExtra(SLOW_RECEIVER_EXTRA, 4); + PendingIntent pi = PendingIntent.getBroadcast(ActivityTestMain.this, 1, intent, 0); + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + long now = SystemClock.elapsedRealtime(); + Log.i(TAG, "Setting alarm for now + 5 seconds"); + am.setExact(AlarmManager.ELAPSED_REALTIME, now + 5_000, pi); + } break; } super.handleMessage(msg); } }; + final BroadcastReceiver mSlowReceiverCompletion = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int extra = intent.getIntExtra(SLOW_RECEIVER_EXTRA, -1); + final String msg = "Slow receiver " + extra + " completed"; + Toast.makeText(ActivityTestMain.this, msg, Toast.LENGTH_LONG) + .show(); + Log.i(TAG, msg); + } + }; + class BroadcastResultReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -387,6 +436,18 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Slow receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + scheduleSlowReceiver(); + return true; + } + }); + menu.add("Slow alarm receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + scheduleSlowAlarmReceiver(); + return true; + } + }); menu.add("Spam!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { scheduleSpam(false); @@ -469,6 +530,7 @@ public class ActivityTestMain extends Activity { protected void onStop() { super.onStop(); mHandler.removeMessages(MSG_SPAM_ALARM); + mHandler.removeMessages(MSG_SLOW_RECEIVER); for (ServiceConnection conn : mConnections) { unbindService(conn); } @@ -544,6 +606,16 @@ public class ActivityTestMain extends Activity { mHandler.sendMessageDelayed(msg, delay); } + void scheduleSlowReceiver() { + mHandler.removeMessages(MSG_SLOW_RECEIVER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_RECEIVER), 500); + } + + void scheduleSlowAlarmReceiver() { + mHandler.removeMessages(MSG_SLOW_ALARM_RECEIVER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_ALARM_RECEIVER), 500); + } + private View scrollWrap(View view) { ScrollView scroller = new ScrollView(this); scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java new file mode 100644 index 000000000000..0437a289741c --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.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.google.android.test.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.util.Log; + +public class SlowReceiver extends BroadcastReceiver { + private static final String TAG = "SlowReceiver"; + private static final long RECEIVER_DELAY = 6_000; + + @Override + public void onReceive(Context context, Intent intent) { + final int extra = intent.getIntExtra(ActivityTestMain.SLOW_RECEIVER_EXTRA, -1); + if (extra == 1) { + Log.i(TAG, "Received broadcast 1; delaying return by " + RECEIVER_DELAY + " ms"); + long now = SystemClock.elapsedRealtime(); + final long end = now + RECEIVER_DELAY; + while (now < end) { + try { + Thread.sleep(end - now); + } catch (InterruptedException e) { } + now = SystemClock.elapsedRealtime(); + } + } else { + Log.i(TAG, "Extra parameter not 1, returning immediately"); + } + Log.i(TAG, "Returning from onReceive()"); + } +} diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk index 979d13ac9405..ee02a72b6819 100644 --- a/tests/DexLoggerIntegrationTests/Android.mk +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -35,7 +35,6 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_MODULE := DexLoggerNativeTestLibrary -LOCAL_MULTILIB := first LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) @@ -44,8 +43,6 @@ LOCAL_NDK_STL_VARIANT := c++_static include $(BUILD_SHARED_LIBRARY) -dexloggertest_so := $(LOCAL_BUILT_MODULE) - # And a standalone native executable that we can exec. include $(CLEAR_VARS) @@ -73,11 +70,15 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ truth-prebuilt \ +# Include both versions of the .so if we have 2 arch +LOCAL_MULTILIB := both +LOCAL_JNI_SHARED_LIBRARIES := \ + DexLoggerNativeTestLibrary \ + # This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various # native binaries. LOCAL_JAVA_RESOURCE_FILES := \ $(dexloggertest_jar) \ - $(dexloggertest_so) \ - $(dexloggertest_executable) + $(dexloggertest_executable) \ include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java index d68769b378b9..e92cc56322eb 100644 --- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.app.UiAutomation; import android.content.Context; +import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; @@ -147,7 +148,7 @@ public final class DexLoggerIntegrationTests { String expectedNameHash = "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E"; String expectedContentHash = - copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile); System.load(privateCopyFile.toString()); @@ -170,7 +171,7 @@ public final class DexLoggerIntegrationTests { String expectedNameHash = "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA"; String expectedContentHash = - copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile); System.load(privateCopyFile.toString()); @@ -307,6 +308,12 @@ public final class DexLoggerIntegrationTests { return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name); } + private String libraryPath(final String libraryName) { + // This may be deprecated. but it tells us the ABI of this process which is exactly what we + // want. + return "/lib/" + Build.CPU_ABI + "/" + libraryName; + } + private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception { MessageDigest hasher = MessageDigest.getInstance("SHA-256"); diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index ec07037b3a8f..86af6422dad3 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -26,8 +26,8 @@ import android.os.test.TestLooper; import android.support.test.InstrumentationRegistry; import com.android.server.PackageWatchdog.PackageHealthObserver; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; -import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -45,23 +45,22 @@ import java.util.concurrent.TimeUnit; public class PackageWatchdogTest { private static final String APP_A = "com.package.a"; private static final String APP_B = "com.package.b"; + private static final String APP_C = "com.package.c"; + private static final String APP_D = "com.package.d"; private static final String OBSERVER_NAME_1 = "observer1"; private static final String OBSERVER_NAME_2 = "observer2"; private static final String OBSERVER_NAME_3 = "observer3"; + private static final String OBSERVER_NAME_4 = "observer4"; private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); private TestLooper mTestLooper; @Before public void setUp() throws Exception { - mTestLooper = new TestLooper(); - mTestLooper.startAutoDispatch(); - } - - @After - public void tearDown() throws Exception { new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); + mTestLooper = new TestLooper(); + mTestLooper.startAutoDispatch(); } /** @@ -154,7 +153,6 @@ public class PackageWatchdogTest { assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); - // Then advance time and run IO Handler so file is saved mTestLooper.dispatchAll(); @@ -198,47 +196,191 @@ public class PackageWatchdogTest { watchdog.onPackageFailure(new String[]{APP_A}); } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + // Verify that observers are not notified assertEquals(0, observer1.mFailedPackages.size()); assertEquals(0, observer2.mFailedPackages.size()); } /** - * Test package failure and notifies all observer since none handles the failure + * Test package failure and does not notify any observer because they are not observing + * the failed packages. */ @Test - public void testPackageFailureNotifyAll() throws Exception { + public void testPackageFailureNotifyNone() throws Exception { PackageWatchdog watchdog = createWatchdog(); - TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); - TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_HIGH); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_HIGH); + - // Start observing for observer1 and observer2 without handling failures watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); + + // Then fail APP_C (not observed) above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_C}); + } + + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); - // Then fail APP_A and APP_B above the threshold + // Verify that observers are not notified + assertEquals(0, observer1.mFailedPackages.size()); + assertEquals(0, observer2.mFailedPackages.size()); + } + + /** + * Test package failure and notifies only least impact observers. + */ + @Test + public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_NONE); + TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_HIGH); + TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, + PackageHealthObserverImpact.USER_IMPACT_LOW); + + // Start observing for all impact observers + watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), + SHORT_DURATION); + watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), + SHORT_DURATION); + watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), + SHORT_DURATION); + watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), + SHORT_DURATION); + + // Then fail all apps above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { - watchdog.onPackageFailure(new String[]{APP_A, APP_B}); + watchdog.onPackageFailure(new String[]{APP_A, APP_B, APP_C, APP_D}); } - // Verify all observers are notifed of all package failures - List<String> observer1Packages = observer1.mFailedPackages; - List<String> observer2Packages = observer2.mFailedPackages; - assertEquals(2, observer1Packages.size()); - assertEquals(1, observer2Packages.size()); - assertEquals(APP_A, observer1Packages.get(0)); - assertEquals(APP_B, observer1Packages.get(1)); - assertEquals(APP_A, observer2Packages.get(0)); + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify least impact observers are notifed of package failures + List<String> observerNonePackages = observerNone.mFailedPackages; + List<String> observerHighPackages = observerHigh.mFailedPackages; + List<String> observerMidPackages = observerMid.mFailedPackages; + List<String> observerLowPackages = observerLow.mFailedPackages; + + // APP_D failure observed by only observerNone is not caught cos its impact is none + assertEquals(0, observerNonePackages.size()); + // APP_C failure is caught by observerHigh cos it's the lowest impact observer + assertEquals(1, observerHighPackages.size()); + assertEquals(APP_C, observerHighPackages.get(0)); + // APP_B failure is caught by observerMid cos it's the lowest impact observer + assertEquals(1, observerMidPackages.size()); + assertEquals(APP_B, observerMidPackages.get(0)); + // APP_A failure is caught by observerLow cos it's the lowest impact observer + assertEquals(1, observerLowPackages.size()); + assertEquals(APP_A, observerLowPackages.get(0)); } /** - * Test package failure and notifies only one observer because it handles the failure + * Test package failure and least impact observers are notified successively. + * State transistions: + * + * <ul> + * <li>(observer1:low, observer2:mid) -> {observer1} + * <li>(observer1:high, observer2:mid) -> {observer2} + * <li>(observer1:high, observer2:none) -> {observer1} + * <li>(observer1:none, observer2:none) -> {} + * <ul> */ @Test - public void testPackageFailureNotifyOne() throws Exception { + public void testPackageFailureNotifyLeastSuccessively() throws Exception { PackageWatchdog watchdog = createWatchdog(); - TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */); - TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */); + TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LOW); + TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + + // Start observing for observerFirst and observerSecond with failure handling + watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); + + // Then fail APP_A above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify only observerFirst is notifed + assertEquals(1, observerFirst.mFailedPackages.size()); + assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); + assertEquals(0, observerSecond.mFailedPackages.size()); + + // After observerFirst handles failure, next action it has is high impact + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; + observerFirst.mFailedPackages.clear(); + observerSecond.mFailedPackages.clear(); + + // Then fail APP_A again above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify only observerSecond is notifed cos it has least impact + assertEquals(1, observerSecond.mFailedPackages.size()); + assertEquals(APP_A, observerSecond.mFailedPackages.get(0)); + assertEquals(0, observerFirst.mFailedPackages.size()); + + // After observerSecond handles failure, it has no further actions + observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerFirst.mFailedPackages.clear(); + observerSecond.mFailedPackages.clear(); + + // Then fail APP_A again above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify only observerFirst is notifed cos it has the only action + assertEquals(1, observerFirst.mFailedPackages.size()); + assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); + assertEquals(0, observerSecond.mFailedPackages.size()); + + // After observerFirst handles failure, it too has no further actions + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerFirst.mFailedPackages.clear(); + observerSecond.mFailedPackages.clear(); + + // Then fail APP_A again above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify no observer is notified cos no actions left + assertEquals(0, observerFirst.mFailedPackages.size()); + assertEquals(0, observerSecond.mFailedPackages.size()); + } + + /** + * Test package failure and notifies only one observer even with observer impact tie. + */ + @Test + public void testPackageFailureNotifyOneSameImpact() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_HIGH); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_HIGH); // Start observing for observer1 and observer2 with failure handling watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); @@ -249,6 +391,9 @@ public class PackageWatchdogTest { watchdog.onPackageFailure(new String[]{APP_A}); } + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + // Verify only one observer is notifed assertEquals(1, observer1.mFailedPackages.size()); assertEquals(APP_A, observer1.mFailedPackages.get(0)); @@ -262,21 +407,26 @@ public class PackageWatchdogTest { private static class TestObserver implements PackageHealthObserver { private final String mName; - private boolean mShouldHandle; + private int mImpact; final List<String> mFailedPackages = new ArrayList<>(); TestObserver(String name) { mName = name; + mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; } - TestObserver(String name, boolean shouldHandle) { + TestObserver(String name, int impact) { mName = name; - mShouldHandle = shouldHandle; + mImpact = impact; + } + + public int onHealthCheckFailed(String packageName) { + return mImpact; } - public boolean onHealthCheckFailed(String packageName) { + public boolean execute(String packageName) { mFailedPackages.add(packageName); - return mShouldHandle; + return true; } public String getName() { diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java index 030641bf0895..e10f866c899f 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java @@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit; /** * A broadcast receiver that can be used to get - * ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts. + * ACTION_ROLLBACK_COMMITTED broadcasts. */ class RollbackBroadcastReceiver extends BroadcastReceiver { @@ -43,7 +43,7 @@ class RollbackBroadcastReceiver extends BroadcastReceiver { */ RollbackBroadcastReceiver() { IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); + filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED); InstrumentationRegistry.getContext().registerReceiver(this, filter); } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 13ac4f09dd86..0493ef8ab062 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -31,11 +31,8 @@ import android.support.test.InstrumentationRegistry; import android.util.Log; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Ignore; @@ -43,6 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -86,8 +84,8 @@ public class RollbackTest { Manifest.permission.DELETE_PACKAGES, Manifest.permission.MANAGE_ROLLBACKS); - // Register a broadcast receiver for notification when the rollback is - // done executing. + // Register a broadcast receiver for notification when the + // rollback has been committed. RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); RollbackManager rm = RollbackTestUtils.getRollbackManager(); @@ -99,12 +97,11 @@ public class RollbackTest { // uninstalled and when rollback manager deletes the rollback. Fix it // so that's not the case! for (int i = 0; i < 5; ++i) { - for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(info.targetPackage.getPackageName())) { - Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); - Thread.sleep(1000); - break; - } + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + if (rollback != null) { + Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); + Thread.sleep(1000); } } @@ -113,13 +110,11 @@ public class RollbackTest { // between when the app is uninstalled and when the previously // available rollback, if any, is removed. Thread.sleep(1000); - assertNull(rm.getAvailableRollback(TEST_APP_A)); - assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); + assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); - // There should be no recently executed rollbacks for this package. - for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) { - assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName()); - } + // There should be no recently committed rollbacks for this package. + assertNull(getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A)); // Install v1 of the app (without rollbacks enabled). RollbackTestUtils.install("RollbackTestAppAv1.apk", false); @@ -134,10 +129,9 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // We should not have received any rollback requests yet. // TODO: Possibly flaky if, by chance, some other app on device @@ -145,7 +139,7 @@ public class RollbackTest { assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS)); // Roll back the app. - RollbackTestUtils.rollback(rollback); + RollbackTestUtils.rollback(rollback.getRollbackId()); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); // Verify we received a broadcast for the rollback. @@ -156,15 +150,9 @@ public class RollbackTest { assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS)); // Verify the recent rollback has been recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.getPackageName())) { - assertNull(rollback); - rollback = r; - } - } - assertNotNull(rollback); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage); + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); broadcastReceiver.unregister(); context.unregisterReceiver(enableRollbackReceiver); @@ -202,31 +190,28 @@ public class RollbackTest { // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage); + rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage); + rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Rollback of B should not rollback A - RollbackTestUtils.rollback(rollbackB); + RollbackTestUtils.rollback(rollbackB.getRollbackId()); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); } finally { @@ -264,31 +249,26 @@ public class RollbackTest { // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage); + rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollbackA); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B)); - rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage); + rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); // Rollback of B should rollback A as well - RollbackTestUtils.rollback(rollbackB); + RollbackTestUtils.rollback(rollbackB.getRollbackId()); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); } finally { @@ -297,10 +277,10 @@ public class RollbackTest { } /** - * Test that recently executed rollback data is properly persisted. + * Test that recently committed rollback data is properly persisted. */ @Test - public void testRecentlyExecutedRollbackPersistence() throws Exception { + public void testRecentlyCommittedRollbackPersistence() throws Exception { try { RollbackTestUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, @@ -319,37 +299,27 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); // Roll back the app. - RollbackTestUtils.rollback(rollback); + RollbackTestUtils.rollback(rollback.getRollbackId()); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); // Verify the recent rollback has been recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.getPackageName())) { - assertNull(rollback); - rollback = r; - } - } + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); assertNotNull(rollback); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // Reload the persisted data. rm.reloadPersistedData(); // Verify the recent rollback is still recorded. - rollback = null; - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - if (TEST_APP_A.equals(r.targetPackage.getPackageName())) { - assertNull(rollback); - rollback = r; - } - } + rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); assertNotNull(rollback); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } @@ -378,17 +348,16 @@ public class RollbackTest { // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); // Expire the rollback. rm.expireRollbackForPackage(TEST_APP_A); // The rollback should no longer be available. - assertNull(rm.getAvailableRollback(TEST_APP_A)); - assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); + assertNull(getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A)); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } @@ -459,8 +428,9 @@ public class RollbackTest { processUserData(TEST_APP_A); RollbackManager rm = RollbackTestUtils.getRollbackManager(); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - RollbackTestUtils.rollback(rollback); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + RollbackTestUtils.rollback(rollback.getRollbackId()); processUserData(TEST_APP_A); } finally { RollbackTestUtils.dropShellPermissionIdentity(); @@ -469,12 +439,12 @@ public class RollbackTest { /** * Test restrictions on rollback broadcast sender. - * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast. + * A random app should not be able to send a ROLLBACK_COMMITTED broadcast. */ @Test public void testRollbackBroadcastRestrictions() throws Exception { RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); + Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); try { InstrumentationRegistry.getContext().sendBroadcast(broadcast); fail("Succeeded in sending restricted broadcast from app context."); @@ -516,21 +486,21 @@ public class RollbackTest { assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); // Both test apps should now be available for rollback, and the - // targetPackage returned for rollback should be correct. + // RollbackInfo returned for the rollbacks should be correct. // TODO: See if there is a way to remove this race condition // between when the app is installed and when the rollback // is made available. Thread.sleep(1000); - RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollbackA); - assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName()); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName()); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Executing rollback should roll back the correct package. - RollbackTestUtils.rollback(rollbackA); + RollbackTestUtils.rollback(rollbackA.getRollbackId()); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); @@ -539,7 +509,7 @@ public class RollbackTest { RollbackTestUtils.install("RollbackTestAppAv2.apk", true); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - RollbackTestUtils.rollback(rollbackB); + RollbackTestUtils.rollback(rollbackB.getRollbackId()); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); } finally { @@ -558,21 +528,14 @@ public class RollbackTest { RollbackManager rm = RollbackTestUtils.getRollbackManager(); try { - rm.getAvailableRollback(TEST_APP_A); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.getPackagesWithAvailableRollbacks(); + rm.getAvailableRollbacks(); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. } try { - rm.getRecentlyExecutedRollbacks(); + rm.getRecentlyCommittedRollbacks(); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. @@ -581,7 +544,7 @@ public class RollbackTest { try { // TODO: What if the implementation checks arguments for non-null // first? Then this test isn't valid. - rm.executeRollback(null, null); + rm.commitRollback(0, null); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. @@ -629,27 +592,26 @@ public class RollbackTest { assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); // TEST_APP_A should now be available for rollback. - assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A)); - RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A); - assertNotNull(rollback); - - // TODO: Test the dependent apps for rollback are correct once we - // support that in the RollbackInfo API. + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoForAandB(rollback); // Rollback the app. It should cause both test apps to be rolled // back. - RollbackTestUtils.rollback(rollback); + RollbackTestUtils.rollback(rollback.getRollbackId()); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); - // We should not see a recent rollback listed for TEST_APP_B - for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) { - assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName()); - } + // We should see recent rollbacks listed for both A and B. + Thread.sleep(1000); + RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - // TODO: Test the listed dependent apps for the recently executed - // rollback are correct once we support that in the RollbackInfo - // API. + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TEST_APP_B); + assertRollbackInfoForAandB(rollbackB); + + assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId()); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } @@ -697,13 +659,13 @@ public class RollbackTest { // 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 rollbackA = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_A); + assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); - RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); - assertNotNull(rollbackB); - assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName()); + RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TEST_APP_B); + assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes for (int i = 0; i < 5; i++) { @@ -724,4 +686,47 @@ public class RollbackTest { RollbackTestUtils.dropShellPermissionIdentity(); } } + + // Helper function to test the value of a RollbackInfo with single package + private void assertRollbackInfoEquals(String packageName, + long versionRolledBackFrom, long versionRolledBackTo, + RollbackInfo info) { + assertNotNull(info); + assertEquals(1, info.getPackages().size()); + assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo, + info.getPackages().get(0)); + } + + // Helper function to test that the given rollback info is a rollback for + // the atomic set {A2, B2} -> {A1, B1}. + private void assertRollbackInfoForAandB(RollbackInfo rollback) { + assertNotNull(rollback); + assertEquals(2, rollback.getPackages().size()); + if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) { + assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0)); + assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1)); + } else { + assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0)); + assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1)); + } + } + + // Helper function to return the RollbackInfo with a given package in the + // list of rollbacks. Throws an assertion failure if there is more than + // one such rollback info. Returns null if there are no such rollback + // infos. + private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks, + String packageName) { + RollbackInfo found = null; + for (RollbackInfo rollback : rollbacks) { + for (PackageRollbackInfo info : rollback.getPackages()) { + if (packageName.equals(info.getPackageName())) { + assertNull(found); + found = rollback; + break; + } + } + } + return found; + } } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index edb13556b8fc..14786577c814 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.support.test.InstrumentationRegistry; @@ -90,12 +89,12 @@ class RollbackTestUtils { } /** - * Execute the given rollback. + * Commit the given rollback. * @throws AssertionError if the rollback fails. */ - static void rollback(RollbackInfo rollback) throws InterruptedException { + static void rollback(int rollbackId) throws InterruptedException { RollbackManager rm = getRollbackManager(); - rm.executeRollback(rollback, LocalIntentSender.getIntentSender()); + rm.commitRollback(rollbackId, LocalIntentSender.getIntentSender()); assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); } diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 3452819835f5..ba6e0f299057 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -16,161 +16,19 @@ package android.net; -import static android.net.NetworkUtils.getImplicitNetmask; -import static android.net.NetworkUtils.inet4AddressToIntHTH; -import static android.net.NetworkUtils.inet4AddressToIntHTL; -import static android.net.NetworkUtils.intToInet4AddressHTH; -import static android.net.NetworkUtils.intToInet4AddressHTL; -import static android.net.NetworkUtils.netmaskToPrefixLength; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; -import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL; -import static android.net.NetworkUtils.getBroadcastAddress; -import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; - import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.fail; - import android.support.test.runner.AndroidJUnit4; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.TreeSet; - import org.junit.Test; import org.junit.runner.RunWith; +import java.math.BigInteger; +import java.util.TreeSet; + @RunWith(AndroidJUnit4.class) @android.support.test.filters.SmallTest public class NetworkUtilsTest { - - private InetAddress Address(String addr) { - return InetAddress.parseNumericAddress(addr); - } - - private Inet4Address IPv4Address(String addr) { - return (Inet4Address) Address(addr); - } - - @Test - public void testGetImplicitNetmask() { - assertEquals(8, getImplicitNetmask(IPv4Address("4.2.2.2"))); - assertEquals(8, getImplicitNetmask(IPv4Address("10.5.6.7"))); - assertEquals(16, getImplicitNetmask(IPv4Address("173.194.72.105"))); - assertEquals(16, getImplicitNetmask(IPv4Address("172.23.68.145"))); - assertEquals(24, getImplicitNetmask(IPv4Address("192.0.2.1"))); - assertEquals(24, getImplicitNetmask(IPv4Address("192.168.5.1"))); - assertEquals(32, getImplicitNetmask(IPv4Address("224.0.0.1"))); - assertEquals(32, getImplicitNetmask(IPv4Address("255.6.7.8"))); - } - - private void assertInvalidNetworkMask(Inet4Address addr) { - try { - netmaskToPrefixLength(addr); - fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testInet4AddressToIntHTL() { - assertEquals(0, inet4AddressToIntHTL(IPv4Address("0.0.0.0"))); - assertEquals(0x000080ff, inet4AddressToIntHTL(IPv4Address("255.128.0.0"))); - assertEquals(0x0080ff0a, inet4AddressToIntHTL(IPv4Address("10.255.128.0"))); - assertEquals(0x00feff0a, inet4AddressToIntHTL(IPv4Address("10.255.254.0"))); - assertEquals(0xfeffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.254"))); - assertEquals(0xffffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTL() { - assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTL(0)); - assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); - assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); - assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); - assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); - assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); - } - - @Test - public void testInet4AddressToIntHTH() { - assertEquals(0, inet4AddressToIntHTH(IPv4Address("0.0.0.0"))); - assertEquals(0xff800000, inet4AddressToIntHTH(IPv4Address("255.128.0.0"))); - assertEquals(0x0aff8000, inet4AddressToIntHTH(IPv4Address("10.255.128.0"))); - assertEquals(0x0afffe00, inet4AddressToIntHTH(IPv4Address("10.255.254.0"))); - assertEquals(0xc0a8fffe, inet4AddressToIntHTH(IPv4Address("192.168.255.254"))); - assertEquals(0xc0a8ffff, inet4AddressToIntHTH(IPv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTH() { - assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTH(0)); - assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); - assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); - assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); - assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); - assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); - } - - @Test - public void testNetmaskToPrefixLength() { - assertEquals(0, netmaskToPrefixLength(IPv4Address("0.0.0.0"))); - assertEquals(9, netmaskToPrefixLength(IPv4Address("255.128.0.0"))); - assertEquals(17, netmaskToPrefixLength(IPv4Address("255.255.128.0"))); - assertEquals(23, netmaskToPrefixLength(IPv4Address("255.255.254.0"))); - assertEquals(31, netmaskToPrefixLength(IPv4Address("255.255.255.254"))); - assertEquals(32, netmaskToPrefixLength(IPv4Address("255.255.255.255"))); - - assertInvalidNetworkMask(IPv4Address("0.0.0.1")); - assertInvalidNetworkMask(IPv4Address("255.255.255.253")); - assertInvalidNetworkMask(IPv4Address("255.255.0.255")); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTL() { - assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); - assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); - assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); - assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); - assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH() { - assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); - assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); - assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); - assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); - assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { - prefixLengthToV4NetmaskIntHTH(-1); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { - prefixLengthToV4NetmaskIntHTH(33); - } - - private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { - final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); - final int addrInt = inet4AddressToIntHTH(IPv4Address(addr)); - assertEquals(IPv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { - checkAddressMasking("192.168.0.0", "192.168.128.1", 16); - checkAddressMasking("255.240.0.0", "255.255.255.255", 12); - checkAddressMasking("255.255.255.255", "255.255.255.255", 32); - checkAddressMasking("0.0.0.0", "255.255.255.255", 0); - } - @Test public void testRoutedIPv4AddressCount() { final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator()); @@ -267,44 +125,4 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } - - @Test - public void testGetPrefixMaskAsAddress() { - assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); - assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); - assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); - assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_PrefixTooLarge() { - getPrefixMaskAsInet4Address(33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_NegativePrefix() { - getPrefixMaskAsInet4Address(-1); - } - - @Test - public void testGetBroadcastAddress() { - assertEquals("192.168.15.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress()); - assertEquals("192.255.255.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress()); - assertEquals("192.168.0.123", - getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress()); - assertEquals("255.255.255.255", - getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress()); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_PrefixTooLarge() { - getBroadcastAddress(IPv4Address("192.168.0.123"), 33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_NegativePrefix() { - getBroadcastAddress(IPv4Address("192.168.0.123"), -1); - } } diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/java/android/net/StaticIpConfigurationTest.java index 5bb573455358..2b5ad378e0ae 100644 --- a/tests/net/java/android/net/StaticIpConfigurationTest.java +++ b/tests/net/java/android/net/StaticIpConfigurationTest.java @@ -26,13 +26,13 @@ import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.net.InetAddress; import java.util.HashSet; import java.util.Objects; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(AndroidJUnit4.class) @SmallTest public class StaticIpConfigurationTest { @@ -203,7 +203,7 @@ public class StaticIpConfigurationTest { try { s.writeToParcel(p, 0); p.setDataPosition(0); - s2 = StaticIpConfiguration.CREATOR.createFromParcel(p); + s2 = StaticIpConfiguration.readFromParcel(p); } finally { p.recycle(); } diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index 80aac047a723..f7542a7b4bfa 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -22,11 +22,11 @@ import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; -import static android.net.NetworkUtils.intToInet4AddressHTH; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.ip.IpServer.STATE_AVAILABLE; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java new file mode 100644 index 000000000000..6da851400af1 --- /dev/null +++ b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; +import static android.net.shared.Inet4AddressUtils.getImplicitNetmask; +import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL; +import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; +import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.fail; + +import android.net.InetAddresses; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Inet4AddressUtilsTest { + + @Test + public void testInet4AddressToIntHTL() { + assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0"))); + assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0"))); + assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0"))); + assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0"))); + assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254"))); + assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTL() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); + } + + @Test + public void testInet4AddressToIntHTH() { + assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0"))); + assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0"))); + assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0"))); + assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0"))); + assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254"))); + assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTH() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); + } + + + @Test + public void testPrefixLengthToV4NetmaskIntHTL() { + assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); + assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); + assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); + assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); + assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH() { + assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); + assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); + assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); + assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); + assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { + prefixLengthToV4NetmaskIntHTH(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { + prefixLengthToV4NetmaskIntHTH(33); + } + + private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { + final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); + final int addrInt = inet4AddressToIntHTH(ipv4Address(addr)); + assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { + checkAddressMasking("192.168.0.0", "192.168.128.1", 16); + checkAddressMasking("255.240.0.0", "255.255.255.255", 12); + checkAddressMasking("255.255.255.255", "255.255.255.255", 32); + checkAddressMasking("0.0.0.0", "255.255.255.255", 0); + } + + @Test + public void testGetImplicitNetmask() { + assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2"))); + assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7"))); + assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105"))); + assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8"))); + } + + private void assertInvalidNetworkMask(Inet4Address addr) { + try { + netmaskToPrefixLength(addr); + fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNetmaskToPrefixLength() { + assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0"))); + assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0"))); + assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0"))); + assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0"))); + assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254"))); + assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255"))); + + assertInvalidNetworkMask(ipv4Address("0.0.0.1")); + assertInvalidNetworkMask(ipv4Address("255.255.255.253")); + assertInvalidNetworkMask(ipv4Address("255.255.0.255")); + } + + @Test + public void testGetPrefixMaskAsAddress() { + assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); + assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); + assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); + assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); + } + + @Test + public void testGetBroadcastAddress() { + assertEquals("192.168.15.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress()); + assertEquals("192.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress()); + assertEquals("192.168.0.123", + getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress()); + assertEquals("255.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_PrefixTooLarge() { + getBroadcastAddress(ipv4Address("192.168.0.123"), 33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_NegativePrefix() { + getBroadcastAddress(ipv4Address("192.168.0.123"), -1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_PrefixTooLarge() { + getPrefixMaskAsInet4Address(33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_NegativePrefix() { + getPrefixMaskAsInet4Address(-1); + } + + private Inet4Address ipv4Address(String addr) { + return (Inet4Address) InetAddresses.parseNumericAddress(addr); + } +} diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java index 14df392cbe07..fb4d43c367db 100644 --- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java +++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java @@ -62,7 +62,7 @@ public class IpConfigurationParcelableUtilTest { mDhcpResults.leaseDuration = 3600; mDhcpResults.mtu = 1450; // Any added DhcpResults field must be included in equals() to be tested properly - assertFieldCountEquals(4, DhcpResults.class); + assertFieldCountEquals(8, DhcpResults.class); } @Test diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 7783e108f674..8f752871355f 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -182,7 +182,8 @@ cc_test_host { defaults: ["aapt2_defaults"], data: [ "integration-tests/CompileTest/**/*", - "integration-tests/CommandTests/**/*" + "integration-tests/CommandTests/**/*", + "integration-tests/ConvertTest/**/*" ], } diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 85f90806752f..7a74ba925ba0 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -284,6 +284,8 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer // The table might be modified by below code. auto converted_table = apk->GetResourceTable(); + std::unordered_set<std::string> files_written; + // Resources for (const auto& package : converted_table->packages) { for (const auto& type : package->types) { @@ -297,10 +299,14 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer return 1; } - if (!serializer->SerializeFile(file, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) - << "failed to serialize file " << *file->path); - return 1; + // Only serialize if we haven't seen this file before + if (files_written.insert(*file->path).second) { + if (!serializer->SerializeFile(file, output_writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize file " + << *file->path); + return 1; + } } } // file } // config_value diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp index 8da5bb8d5dd6..3c0fe370c516 100644 --- a/tools/aapt2/cmd/Convert_test.cpp +++ b/tools/aapt2/cmd/Convert_test.cpp @@ -18,6 +18,7 @@ #include "LoadedApk.h" #include "test/Test.h" +#include "ziparchive/zip_archive.h" using testing::Eq; using testing::Ne; @@ -103,4 +104,45 @@ TEST_F(ConvertTest, KeepRawXmlStrings) { EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); } +TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) { + StdErrDiagnostics diag; + const std::string apk_path = + file::BuildPath({android::base::GetExecutableDirectory(), + "integration-tests", "ConvertTest", "duplicate_entries.apk"}); + + const std::string out_convert_apk = GetTestPath("out_convert.apk"); + std::vector<android::StringPiece> convert_args = { + "-o", out_convert_apk, + "--output-format", "proto", + apk_path + }; + ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0)); + + ZipArchiveHandle handle; + ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0)); + + void* cookie = nullptr; + + ZipString prefix("res/theme/10"); + int32_t result = StartIteration(handle, &cookie, &prefix, nullptr); + + // If this is -5, that means we've found a duplicate entry and this test has failed + EXPECT_THAT(result, Eq(0)); + + // But if read succeeds, verify only one res/theme/10 entry + int count = 0; + + // Can't pass nullptrs into Next() + ZipString zip_name; + ZipEntry zip_data; + + while ((result = Next(cookie, &zip_data, &zip_name)) == 0) { + count++; + } + + EndIteration(cookie); + + EXPECT_THAT(count, Eq(1)); +} + } // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk Binary files differnew file mode 100644 index 000000000000..c558a334b369 --- /dev/null +++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp index 961b47cdfecd..cee0cd52a546 100644 --- a/tools/bit/aapt.cpp +++ b/tools/bit/aapt.cpp @@ -159,10 +159,11 @@ int inspect_apk(Apk* apk, const string& filename) { // Load the manifest xml - Command cmd("aapt"); + Command cmd("aapt2"); cmd.AddArg("dump"); cmd.AddArg("xmltree"); cmd.AddArg(filename); + cmd.AddArg("--file"); cmd.AddArg("AndroidManifest.xml"); int err; @@ -217,11 +218,11 @@ inspect_apk(Apk* apk, const string& filename) if (current != NULL) { Attribute attr; string str = match[2]; - size_t colon = str.find(':'); + size_t colon = str.rfind(':'); if (colon == string::npos) { attr.name = str; } else { - attr.ns = scope->namespaces[string(str, 0, colon)]; + attr.ns.assign(str, 0, colon); attr.name.assign(str, colon+1, string::npos); } attr.value = match[3]; diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index a71cea1c44f9..860094aef1f4 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -623,12 +623,13 @@ run_phases(vector<Target*> targets, const Options& options) const string buildProduct = get_required_env("TARGET_PRODUCT", false); const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); const string buildType = get_required_env("TARGET_BUILD_TYPE", false); - + const string buildOut = get_out_dir(); chdir_or_exit(buildTop.c_str()); - const string buildDevice = get_build_var("TARGET_DEVICE", false); - const string buildId = get_build_var("BUILD_ID", false); - const string buildOut = get_out_dir(); + BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType); + + const string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false); + const string buildId = buildVars.GetBuildVar("BUILD_ID", false); // Get the modules for the targets map<string,Module> modules; @@ -661,6 +662,7 @@ run_phases(vector<Target*> targets, const Options& options) string dataPath = buildOut + "/target/product/" + buildDevice + "/data/"; bool syncSystem = false; bool alwaysSyncSystem = false; + vector<string> systemFiles; vector<InstallApk> installApks; for (size_t i=0; i<targets.size(); i++) { Target* target = targets[i]; @@ -670,6 +672,7 @@ run_phases(vector<Target*> targets, const Options& options) // System partition if (starts_with(file, systemPath)) { syncSystem = true; + systemFiles.push_back(file); if (!target->build) { // If a system partition target didn't get built then // it won't change we will always need to do adb sync @@ -692,6 +695,19 @@ run_phases(vector<Target*> targets, const Options& options) get_directory_contents(systemPath, &systemFilesBefore); } + if (systemFiles.size() > 0){ + print_info("System files:"); + for (size_t i=0; i<systemFiles.size(); i++) { + printf(" %s\n", systemFiles[i].c_str()); + } + } + if (installApks.size() > 0){ + print_info("APKs to install:"); + for (size_t i=0; i<installApks.size(); i++) { + printf(" %s\n", installApks[i].file.filename.c_str()); + } + } + // // Build // @@ -798,7 +814,8 @@ run_phases(vector<Target*> targets, const Options& options) for (size_t j=0; j<target->module.installed.size(); j++) { string filename = target->module.installed[j]; - if (!ends_with(filename, ".apk")) { + // Apk in the data partition + if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) { continue; } @@ -1004,13 +1021,16 @@ run_phases(vector<Target*> targets, const Options& options) void run_tab_completion(const string& word) { - const string buildTop = get_required_env("ANDROID_BUILD_TOP", true); + const string buildTop = get_required_env("ANDROID_BUILD_TOP", false); const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); + const string buildType = get_required_env("TARGET_BUILD_TYPE", false); const string buildOut = get_out_dir(); - chdir_or_exit(buildTop.c_str()); - string buildDevice = sniff_device_name(buildOut, buildProduct); + BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType); + + string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false); map<string,Module> modules; read_modules(buildOut, buildDevice, &modules, true); diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index 5a9ab22719cb..627091321b2e 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -21,6 +21,7 @@ #include "util.h" #include <json/reader.h> +#include <json/writer.h> #include <json/value.h> #include <fstream> @@ -34,22 +35,118 @@ using namespace std; -map<string,string> g_buildVars; +static bool +map_contains(const map<string,string>& m, const string& k, const string& v) { + map<string,string>::const_iterator it = m.find(k); + if (it == m.end()) { + return false; + } + return it->second == v; +} + +static string +make_cache_filename(const string& outDir) +{ + string filename(outDir); + return filename + "/.bit_cache"; +} + +BuildVars::BuildVars(const string& outDir, const string& buildProduct, + const string& buildVariant, const string& buildType) + :m_filename(), + m_cache() +{ + m_cache["TARGET_PRODUCT"] = buildProduct; + m_cache["TARGET_BUILD_VARIANT"] = buildVariant; + m_cache["TARGET_BUILD_TYPE"] = buildType; + + // If we have any problems reading the file, that's ok, just do + // uncached calls to make / soong. + + if (outDir == "") { + return; + } + + + m_filename = make_cache_filename(outDir); + + std::ifstream stream(m_filename, std::ifstream::binary); + + if (stream.fail()) { + return; + } + + Json::Value json; + Json::Reader reader; + if (!reader.parse(stream, json)) { + return; + } + + if (!json.isObject()) { + return; + } + + map<string,string> cache; + + vector<string> names = json.getMemberNames(); + const int N = names.size(); + for (int i=0; i<N; i++) { + const string& name = names[i]; + const Json::Value& value = json[name]; + if (!value.isString()) { + continue; + } + cache[name] = value.asString(); + } + + // If all of the base variables match, then we can use this cache. Otherwise, use our + // base one. The next time someone reads a value, the new one, with our base varaibles + // will be saved. + if (map_contains(cache, "TARGET_PRODUCT", buildProduct) + && map_contains(cache, "TARGET_BUILD_VARIANT", buildVariant) + && map_contains(cache, "TARGET_BUILD_TYPE", buildType)) { + m_cache = cache; + } +} + +BuildVars::~BuildVars() +{ +} + +void +BuildVars::save() +{ + if (m_filename == "") { + return; + } + + Json::StyledStreamWriter writer(" "); + + Json::Value json(Json::objectValue); + + for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) { + json[it->first] = it->second; + } + + std::ofstream stream(m_filename, std::ofstream::binary); + writer.write(stream, json); +} string -get_build_var(const string& name, bool quiet) +BuildVars::GetBuildVar(const string& name, bool quiet) { int err; - map<string,string>::iterator it = g_buildVars.find(name); - if (it == g_buildVars.end()) { + map<string,string>::iterator it = m_cache.find(name); + if (it == m_cache.end()) { Command cmd("build/soong/soong_ui.bash"); cmd.AddArg("--dumpvar-mode"); cmd.AddArg(name); string output = trim(get_command_output(cmd, &err, quiet)); if (err == 0) { - g_buildVars[name] = output; + m_cache[name] = output; + save(); return output; } else { return string(); @@ -59,38 +156,6 @@ get_build_var(const string& name, bool quiet) } } -string -sniff_device_name(const string& buildOut, const string& product) -{ - string match("ro.build.product=" + product); - - string base(buildOut + "/target/product"); - DIR* dir = opendir(base.c_str()); - if (dir == NULL) { - return string(); - } - - dirent* entry; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') { - continue; - } - if (entry->d_type == DT_DIR) { - string filename(base + "/" + entry->d_name + "/system/build.prop"); - vector<string> lines; - split_lines(&lines, read_file(filename)); - for (size_t i=0; i<lines.size(); i++) { - if (lines[i] == match) { - return entry->d_name; - } - } - } - } - - closedir(dir); - return string(); -} - void json_error(const string& filename, const char* error, bool quiet) { diff --git a/tools/bit/make.h b/tools/bit/make.h index 1c9504d62d46..db0b69f88a0e 100644 --- a/tools/bit/make.h +++ b/tools/bit/make.h @@ -31,16 +31,26 @@ struct Module vector<string> installed; }; -string get_build_var(const string& name, bool quiet); - /** - * Poke around in the out directory and try to find a device name that matches - * our product. This is faster than running get_build_var and good enough for - * tab completion. - * - * Returns the empty string if we can't find one. + * Class to encapsulate getting build variables. Caches the + * results if possible. */ -string sniff_device_name(const string& buildOut, const string& product); +class BuildVars +{ +public: + BuildVars(const string& outDir, const string& buildProduct, + const string& buildVariant, const string& buildType); + ~BuildVars(); + + string GetBuildVar(const string& name, bool quiet); + +private: + void save(); + + string m_filename; + + map<string,string> m_cache; +}; void read_modules(const string& buildOut, const string& buildDevice, map<string,Module>* modules, bool quiet); diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp index 790e0b4b227e..35feda11ec29 100644 --- a/tools/bit/print.cpp +++ b/tools/bit/print.cpp @@ -116,6 +116,20 @@ print_warning(const char* format, ...) } void +print_info(const char* format, ...) +{ + fputs(g_escapeBold, stdout); + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + + fputs(g_escapeEndColor, stdout); + fputc('\n', stdout); +} + +void print_one_line(const char* format, ...) { if (g_stdoutIsTty) { diff --git a/tools/bit/print.h b/tools/bit/print.h index b6c3e9aa27fa..db6cf5f65cf8 100644 --- a/tools/bit/print.h +++ b/tools/bit/print.h @@ -33,6 +33,7 @@ void print_status(const char* format, ...); void print_command(const Command& command); void print_error(const char* format, ...); void print_warning(const char* format, ...); +void print_info(const char* format, ...); void print_one_line(const char* format, ...); void check_error(int err); diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java index fc4cd01a5a2a..2690ee87f5b2 100644 --- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java @@ -16,8 +16,12 @@ package android.processor.view.inspector; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; @@ -102,6 +106,83 @@ final class AnnotationUtils { } /** + * Get a typed list of values for an annotation array property by name. + * + * The returned list will be empty if the value was left at the default. + * + * @param propertyName The name of the property to search for + * @param valueClass The expected class of the property value + * @param element The element the annotation is on, used for exceptions + * @param annotationMirror An annotation mirror to search for the property + * @param <T> The type of the value + * @return A list containing the requested types + */ + <T> List<T> typedArrayValuesByName( + String propertyName, + Class<T> valueClass, + Element element, + AnnotationMirror annotationMirror) { + return untypedArrayValuesByName(propertyName, element, annotationMirror) + .stream() + .map(annotationValue -> { + final Object value = annotationValue.getValue(); + + if (value == null) { + throw new ProcessingException( + "Unexpected null in array.", + element, + annotationMirror, + annotationValue); + } + + if (valueClass.isAssignableFrom(value.getClass())) { + return valueClass.cast(value); + } else { + throw new ProcessingException( + String.format( + "Expected array entry to have type %s, but got %s.", + valueClass.getCanonicalName(), + value.getClass().getCanonicalName()), + element, + annotationMirror, + annotationValue); + } + }) + .collect(Collectors.toList()); + } + + /** + * Get a list of values for an annotation array property by name. + * + * @param propertyName The name of the property to search for + * @param element The element the annotation is on, used for exceptions + * @param annotationMirror An annotation mirror to search for the property + * @return A list of annotation values, empty list if none found + */ + List<AnnotationValue> untypedArrayValuesByName( + String propertyName, + Element element, + AnnotationMirror annotationMirror) { + return typedValueByName(propertyName, List.class, element, annotationMirror) + .map(untypedValues -> { + List<AnnotationValue> typedValues = new ArrayList<>(untypedValues.size()); + + for (Object untypedValue : untypedValues) { + if (untypedValue instanceof AnnotationValue) { + typedValues.add((AnnotationValue) untypedValue); + } else { + throw new ProcessingException( + "Unable to convert array entry to AnnotationValue", + element, + annotationMirror); + } + } + + return typedValues; + }).orElseGet(Collections::emptyList); + } + + /** * Get the typed value of an annotation property by name. * * The returned optional will be empty if the value was left at the default, or if the value diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java index f1ebb87fed4d..6f588935c44c 100644 --- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java @@ -19,7 +19,9 @@ package android.processor.view.inspector; import com.squareup.javapoet.ClassName; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -92,6 +94,8 @@ public final class InspectableClassModel { private final Type mType; private boolean mAttributeIdInferrableFromR = true; private int mAttributeId = 0; + private List<IntEnumEntry> mIntEnumEntries; + private List<IntFlagEntry> mIntFlagEntries; public Property(String name, String getter, Type type) { mName = Objects.requireNonNull(name, "Name must not be null"); @@ -133,6 +137,40 @@ public final class InspectableClassModel { return mType; } + /** + * Get the mapping for an {@code int} enumeration, if present. + * + * @return A list of mapping entries, empty if absent + */ + public List<IntEnumEntry> getIntEnumEntries() { + if (mIntEnumEntries != null) { + return mIntEnumEntries; + } else { + return Collections.emptyList(); + } + } + + public void setIntEnumEntries(List<IntEnumEntry> intEnumEntries) { + mIntEnumEntries = intEnumEntries; + } + + /** + * Get the mapping of {@code int} flags, if present. + * + * @return A list of mapping entries, empty if absent + */ + public List<IntFlagEntry> getIntFlagEntries() { + if (mIntFlagEntries != null) { + return mIntFlagEntries; + } else { + return Collections.emptyList(); + } + } + + public void setIntFlagEntries(List<IntFlagEntry> intFlagEntries) { + mIntFlagEntries = intFlagEntries; + } + public enum Type { /** Primitive or boxed {@code boolean} */ BOOLEAN, @@ -181,6 +219,7 @@ public final class InspectableClassModel { * An enumeration packed into an {@code int}. * * @see android.view.inspector.IntEnumMapping + * @see IntEnumEntry */ INT_ENUM, @@ -188,8 +227,74 @@ public final class InspectableClassModel { * Non-exclusive or partially-exclusive flags packed into an {@code int}. * * @see android.view.inspector.IntFlagMapping + * @see IntFlagEntry */ INT_FLAG } } + + /** + * Model one entry in a int enum mapping. + * + * @see android.view.inspector.IntEnumMapping + */ + public static final class IntEnumEntry { + private final String mName; + private final int mValue; + + public IntEnumEntry(String name, int value) { + mName = Objects.requireNonNull(name, "Name must not be null"); + mValue = value; + } + + public String getName() { + return mName; + } + + public int getValue() { + return mValue; + } + } + + /** + * Model one entry in an int flag mapping. + * + * @see android.view.inspector.IntFlagMapping + */ + public static final class IntFlagEntry { + private final String mName; + private final int mTarget; + private final int mMask; + + public IntFlagEntry(String name, int target, int mask) { + mName = Objects.requireNonNull(name, "Name must not be null"); + mTarget = target; + mMask = mask; + } + + public IntFlagEntry(String name, int target) { + this(name, target, target); + } + + /** + * Determine if this entry has a bitmask. + * + * @return True if the bitmask and target are different, false otherwise + */ + public boolean hasMask() { + return mTarget != mMask; + } + + public String getName() { + return mName; + } + + public int getTarget() { + return mTarget; + } + + public int getMask() { + return mMask; + } + } } diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java index f666be7a2a61..42ae890640cb 100644 --- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java @@ -16,13 +16,19 @@ package android.processor.view.inspector; +import android.processor.view.inspector.InspectableClassModel.IntEnumEntry; +import android.processor.view.inspector.InspectableClassModel.IntFlagEntry; import android.processor.view.inspector.InspectableClassModel.Property; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -63,6 +69,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor { /** * Set of android and androidx annotation qualified names for colors packed into {@code long}. + * * @see android.annotation.ColorLong */ private static final String[] COLOR_LONG_ANNOTATION_NAMES = { @@ -71,7 +78,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor { /** * @param annotationQualifiedName The qualified name of the annotation to process - * @param processingEnv The processing environment from the parent processor + * @param processingEnv The processing environment from the parent processor */ public InspectablePropertyProcessor( String annotationQualifiedName, @@ -109,8 +116,8 @@ public final class InspectablePropertyProcessor implements ModelProcessor { * Check that an element is shaped like a getter. * * @param element An element that hopefully represents a getter - * @throws ProcessingException if the element isn't a getter * @return An {@link ExecutableElement} that represents a getter method. + * @throws ProcessingException if the element isn't a getter */ private ExecutableElement ensureGetter(Element element) { if (element.getKind() != ElementKind.METHOD) { @@ -144,7 +151,7 @@ public final class InspectablePropertyProcessor implements ModelProcessor { throw new ProcessingException( String.format( "Expected a getter method to take no parameters, " - + "but got %d parameters.", + + "but got %d parameters.", method.getParameters().size()), element); } @@ -167,10 +174,10 @@ public final class InspectablePropertyProcessor implements ModelProcessor { /** * Build a {@link Property} from a getter and an inspectable property annotation. * - * @param getter An element representing the getter to build from + * @param getter An element representing the getter to build from * @param annotation A mirror of an inspectable property-shaped annotation - * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled * @return A property for the getter and annotation + * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled */ private Property buildProperty(ExecutableElement getter, AnnotationMirror annotation) { final String name = mAnnotationUtils @@ -190,16 +197,25 @@ public final class InspectablePropertyProcessor implements ModelProcessor { .typedValueByName("attributeId", Integer.class, getter, annotation) .ifPresent(property::setAttributeId); + switch (property.getType()) { + case INT_ENUM: + property.setIntEnumEntries(processEnumMapping(getter, annotation)); + break; + case INT_FLAG: + property.setIntFlagEntries(processFlagMapping(getter, annotation)); + break; + } + return property; } /** * Determine the property type from the annotation, return type, or context clues. * - * @param getter An element representing the getter to build from + * @param getter An element representing the getter to build from * @param annotation A mirror of an inspectable property-shaped annotation * @return The resolved property type - * @throws ProcessingException If the property type cannot be resolved + * @throws ProcessingException If the property type cannot be resolved or is invalid * @see android.view.inspector.InspectableProperty#valueType() */ private Property.Type determinePropertyType( @@ -213,10 +229,62 @@ public final class InspectablePropertyProcessor implements ModelProcessor { final Property.Type returnType = convertReturnTypeToPropertyType(getter); + final boolean hasColor = hasColorAnnotation(getter); + final Optional<AnnotationValue> enumMapping = + mAnnotationUtils.valueByName("enumMapping", annotation); + final Optional<AnnotationValue> flagMapping = + mAnnotationUtils.valueByName("flagMapping", annotation); + + if (returnType != Property.Type.INT) { + enumMapping.ifPresent(value -> { + throw new ProcessingException( + String.format( + "Can only use enumMapping on int types, got %s.", + returnType.toString().toLowerCase()), + getter, + annotation, + value); + }); + flagMapping.ifPresent(value -> { + throw new ProcessingException( + String.format( + "Can only use flagMapping on int types, got %s.", + returnType.toString().toLowerCase()), + getter, + annotation, + value); + }); + } + switch (valueType) { case "INFERRED": - if (hasColorAnnotation(getter)) { + if (hasColor) { + enumMapping.ifPresent(value -> { + throw new ProcessingException( + "Cannot use enumMapping on a color type.", + getter, + annotation, + value); + }); + flagMapping.ifPresent(value -> { + throw new ProcessingException( + "Cannot use flagMapping on a color type.", + getter, + annotation, + value); + }); return Property.Type.COLOR; + } else if (enumMapping.isPresent()) { + flagMapping.ifPresent(value -> { + throw new ProcessingException( + "Cannot use flagMapping and enumMapping simultaneously.", + getter, + annotation, + value); + }); + return Property.Type.INT_ENUM; + } else if (flagMapping.isPresent()) { + return Property.Type.INT_FLAG; } else { return returnType; } @@ -235,17 +303,14 @@ public final class InspectablePropertyProcessor implements ModelProcessor { annotation); } case "GRAVITY": - if (returnType == Property.Type.INT) { - return Property.Type.GRAVITY; - } else { - throw new ProcessingException( - String.format("Gravity must be an integer, got %s", returnType), - getter, - annotation); - } + requirePackedIntToReturnInt("Gravity", returnType, getter, annotation); + return Property.Type.GRAVITY; case "INT_ENUM": + requirePackedIntToReturnInt("IntEnum", returnType, getter, annotation); + return Property.Type.INT_ENUM; case "INT_FLAG": - throw new ProcessingException("Not implemented", getter, annotation); + requirePackedIntToReturnInt("IntFlag", returnType, getter, annotation); + return Property.Type.INT_FLAG; default: throw new ProcessingException( String.format("Unknown value type enumeration value: %s", valueType), @@ -258,8 +323,8 @@ public final class InspectablePropertyProcessor implements ModelProcessor { * Get a property type from the return type of a getter. * * @param getter The getter to extract the return type of - * @throws ProcessingException If the return type is not a primitive or an object * @return The property type returned by the getter + * @throws ProcessingException If the return type is not a primitive or an object */ private Property.Type convertReturnTypeToPropertyType(ExecutableElement getter) { final TypeMirror returnType = getter.getReturnType(); @@ -295,6 +360,31 @@ public final class InspectablePropertyProcessor implements ModelProcessor { } /** + * Require that a value type packed into an integer be on a getter that returns an int. + * + * @param typeName The name of the type to use in the exception + * @param returnType The return type of the getter to check + * @param getter The getter, to use in the exception + * @param annotation The annotation, to use in the exception + * @throws ProcessingException If the return type is not an int + */ + private static void requirePackedIntToReturnInt( + String typeName, + Property.Type returnType, + ExecutableElement getter, + AnnotationMirror annotation) { + if (returnType != Property.Type.INT) { + throw new ProcessingException( + String.format( + "%s can only be defined on a method that returns int, got %s.", + typeName, + returnType.toString().toLowerCase()), + getter, + annotation); + } + } + + /** * Determine if a getter is annotated with color annotation matching its return type. * * Note that an {@code int} return value annotated with {@link android.annotation.ColorLong} is @@ -303,7 +393,6 @@ public final class InspectablePropertyProcessor implements ModelProcessor { * * @param getter The getter to query * @return True if the getter has a color annotation, false otherwise - * */ private boolean hasColorAnnotation(ExecutableElement getter) { switch (unboxType(getter.getReturnType())) { @@ -353,6 +442,117 @@ public final class InspectablePropertyProcessor implements ModelProcessor { } /** + * Build a model of an {@code int} enumeration mapping from annotation values. + * + * This method only handles the one-to-one mapping of mirrors of + * {@link android.view.inspector.InspectableProperty.EnumMap} annotations into + * {@link IntEnumEntry} objects. Further validation should be handled elsewhere + * + * @see android.view.inspector.IntEnumMapping + * @see android.view.inspector.InspectableProperty#enumMapping() + * @param getter The getter of the property, used for exceptions + * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to + * extract enum mapping values from. + * @return A list of int enum entries, in the order specified in source + * @throws ProcessingException if mapping doesn't exist or is invalid + */ + private List<IntEnumEntry> processEnumMapping( + ExecutableElement getter, + AnnotationMirror annotation) { + List<AnnotationMirror> enumAnnotations = mAnnotationUtils.typedArrayValuesByName( + "enumMapping", AnnotationMirror.class, getter, annotation); + List<IntEnumEntry> enumEntries = new ArrayList<>(enumAnnotations.size()); + + if (enumAnnotations.isEmpty()) { + throw new ProcessingException( + "Encountered an empty array for enumMapping", getter, annotation); + } + + for (AnnotationMirror enumAnnotation : enumAnnotations) { + final String name = mAnnotationUtils.typedValueByName( + "name", String.class, getter, enumAnnotation) + .orElseThrow(() -> { + throw new ProcessingException( + "Name is required for @EnumMap", + getter, + enumAnnotation); + }); + + final int value = mAnnotationUtils.typedValueByName( + "value", Integer.class, getter, enumAnnotation) + .orElseThrow(() -> { + throw new ProcessingException( + "Value is required for @EnumMap", + getter, + enumAnnotation); + }); + + enumEntries.add(new IntEnumEntry(name, value)); + } + + return enumEntries; + } + + /** + * Build a model of an {@code int} flag mapping from annotation values. + * + * This method only handles the one-to-one mapping of mirrors of + * {@link android.view.inspector.InspectableProperty.FlagMap} annotations into + * {@link IntFlagEntry} objects. Further validation should be handled elsewhere + * + * @see android.view.inspector.IntFlagMapping + * @see android.view.inspector.InspectableProperty#flagMapping() + * @param getter The getter of the property, used for exceptions + * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to + * extract flag mapping values from. + * @return A list of int flags entries, in the order specified in source + * @throws ProcessingException if mapping doesn't exist or is invalid + */ + private List<IntFlagEntry> processFlagMapping( + ExecutableElement getter, + AnnotationMirror annotation) { + List<AnnotationMirror> flagAnnotations = mAnnotationUtils.typedArrayValuesByName( + "flagMapping", AnnotationMirror.class, getter, annotation); + List<IntFlagEntry> flagEntries = new ArrayList<>(flagAnnotations.size()); + + if (flagAnnotations.isEmpty()) { + throw new ProcessingException( + "Encountered an empty array for flagMapping", getter, annotation); + } + + for (AnnotationMirror flagAnnotation : flagAnnotations) { + final String name = mAnnotationUtils.typedValueByName( + "name", String.class, getter, flagAnnotation) + .orElseThrow(() -> { + throw new ProcessingException( + "Name is required for @FlagMap", + getter, + flagAnnotation); + }); + + final int target = mAnnotationUtils.typedValueByName( + "target", Integer.class, getter, flagAnnotation) + .orElseThrow(() -> { + throw new ProcessingException( + "Target is required for @FlagMap", + getter, + flagAnnotation); + }); + + final Optional<Integer> mask = mAnnotationUtils.typedValueByName( + "mask", Integer.class, getter, flagAnnotation); + + if (mask.isPresent()) { + flagEntries.add(new IntFlagEntry(name, target, mask.get())); + } else { + flagEntries.add(new IntFlagEntry(name, target)); + } + } + + return flagEntries; + } + + /** * Determine if a {@link TypeMirror} is a boxed or unboxed boolean. * * @param type The type mirror to check diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java index dd4d8f54fb68..7b04645e9f44 100644 --- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java @@ -16,6 +16,8 @@ package android.processor.view.inspector; +import android.processor.view.inspector.InspectableClassModel.IntEnumEntry; +import android.processor.view.inspector.InspectableClassModel.IntFlagEntry; import android.processor.view.inspector.InspectableClassModel.Property; import com.squareup.javapoet.ClassName; @@ -69,6 +71,18 @@ public final class InspectionCompanionGenerator { "android.view.inspector", "PropertyReader"); /** + * The class name of {@link android.view.inspector.IntEnumMapping}. + */ + private static final ClassName INT_ENUM_MAPPING = ClassName.get( + "android.view.inspector", "IntEnumMapping"); + + /** + * The class name of {@link android.view.inspector.IntFlagMapping}. + */ + private static final ClassName INT_FLAG_MAPPING = ClassName.get( + "android.view.inspector", "IntFlagMapping"); + + /** * The {@code mPropertiesMapped} field. */ private static final FieldSpec M_PROPERTIES_MAPPED = FieldSpec @@ -248,13 +262,13 @@ public final class InspectionCompanionGenerator { final MethodSpec.Builder builder = MethodSpec.methodBuilder("readProperties") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .addParameter(model.getClassName(), "inspectable") + .addParameter(model.getClassName(), "node") .addParameter(PROPERTY_READER, "propertyReader") .addCode(generatePropertyMapInitializationCheck()); for (PropertyIdField propertyIdField : propertyIdFields) { builder.addStatement( - "propertyReader.read$L($N, inspectable.$L())", + "propertyReader.read$L($N, node.$L())", methodSuffixForPropertyType(propertyIdField.mProperty.getType()), propertyIdField.mFieldSpec, propertyIdField.mProperty.getGetter()); @@ -286,21 +300,22 @@ public final class InspectionCompanionGenerator { if (property.getAttributeId() == ID_NULL) { builder.add("$L", ID_NULL); } else { - builder.add("$L", String.format("0x%08x", property.getAttributeId())); + builder.add("$L", hexLiteral(property.getAttributeId())); } } switch (property.getType()) { case INT_ENUM: - throw new RuntimeException("IntEnumMapping generation not implemented"); + builder.add(",$W"); + builder.add(generateIntEnumMappingBuilder(property.getIntEnumEntries())); + break; case INT_FLAG: - throw new RuntimeException("IntFlagMapping generation not implemented"); - default: - builder.add(")"); + builder.add(",$W"); + builder.add(generateIntFlagMappingBuilder(property.getIntFlagEntries())); break; } - return builder.build(); + return builder.add(")").build(); } /** @@ -327,6 +342,56 @@ public final class InspectionCompanionGenerator { } /** + * Generate an invocation of {@link android.view.inspector.IntEnumMapping.Builder}. + * + * <pre> + * new IntEnumMapping.Builder() + * .addValue("ONE", 1) + * .build() + * </pre> + * + * @return A codeblock containing the an int enum mapping builder + */ + private CodeBlock generateIntEnumMappingBuilder(List<IntEnumEntry> intEnumEntries) { + final ArrayList<IntEnumEntry> sortedEntries = new ArrayList<>(intEnumEntries); + sortedEntries.sort(Comparator.comparing(IntEnumEntry::getValue)); + + final CodeBlock.Builder builder = CodeBlock.builder() + .add("new $T()$>", INT_ENUM_MAPPING.nestedClass("Builder")); + + for (IntEnumEntry entry : sortedEntries) { + builder.add("\n.addValue($S, $L)", entry.getName(), entry.getValue()); + } + + return builder.add("\n.build()$<").build(); + } + + private CodeBlock generateIntFlagMappingBuilder(List<IntFlagEntry> intFlagEntries) { + final ArrayList<IntFlagEntry> sortedEntries = new ArrayList<>(intFlagEntries); + sortedEntries.sort(Comparator.comparing(IntFlagEntry::getName)); + + final CodeBlock.Builder builder = CodeBlock.builder() + .add("new $T()$>", INT_FLAG_MAPPING.nestedClass("Builder")); + + for (IntFlagEntry entry : sortedEntries) { + if (entry.hasMask()) { + builder.add( + "\n.addFlag($S, $L, $L)", + entry.getName(), + hexLiteral(entry.getTarget()), + hexLiteral(entry.getMask())); + } else { + builder.add( + "\n.addFlag($S, $L)", + entry.getName(), + hexLiteral(entry.getTarget())); + } + } + + return builder.add("\n.build()$<").build(); + } + + /** * Generate the final class name for the inspection companion from the model's class name. * * The generated class is added to the same package as the source class. If the class in the @@ -385,6 +450,10 @@ public final class InspectionCompanionGenerator { } } + private static String hexLiteral(int value) { + return String.format("0x%08x", value); + } + /** * Value class that holds a {@link Property} and a {@link FieldSpec} for that property. */ diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java index 455f5b08e49e..01d94307f871 100644 --- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java @@ -32,6 +32,7 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -118,6 +119,12 @@ public final class PlatformInspectableProcessor extends AbstractProcessor { break; } + final Set<Modifier> classModifiers = classElement.get().getModifiers(); + + if (classModifiers.contains(Modifier.PRIVATE)) { + fail("Enclosing class cannot be private", element); + } + final InspectableClassModel model = modelMap.computeIfAbsent( classElement.get().getQualifiedName().toString(), k -> new InspectableClassModel(ClassName.get(classElement.get()))); diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java index b0775dc77f45..f6d8bb0939db 100644 --- a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java +++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java @@ -16,11 +16,13 @@ package android.processor.view.inspector; -import android.processor.view.inspector.InspectableClassModel.Property; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.fail; +import android.processor.view.inspector.InspectableClassModel.IntEnumEntry; +import android.processor.view.inspector.InspectableClassModel.IntFlagEntry; +import android.processor.view.inspector.InspectableClassModel.Property; import com.google.common.base.Charsets; import com.google.common.io.Resources; @@ -31,6 +33,7 @@ import org.junit.Test; import java.io.IOException; import java.net.URL; +import java.util.Arrays; import java.util.Optional; /** @@ -40,7 +43,7 @@ public class InspectionCompanionGeneratorTest { private static final String RESOURCE_PATH_TEMPLATE = "android/processor/view/inspector/InspectionCompanionGeneratorTest/%s.java.txt"; private static final ClassName TEST_CLASS_NAME = - ClassName.get("com.android.inspectable", "TestInspectable"); + ClassName.get("com.android.node", "TestNode"); private InspectableClassModel mModel; private InspectionCompanionGenerator mGenerator; @@ -59,7 +62,7 @@ public class InspectionCompanionGeneratorTest { @Test public void testNestedClass() { mModel = new InspectableClassModel( - ClassName.get("com.android.inspectable", "Outer", "Inner")); + ClassName.get("com.android.node", "Outer", "Inner")); assertGeneratedFileEquals("NestedClass"); } @@ -105,6 +108,42 @@ public class InspectionCompanionGeneratorTest { assertGeneratedFileEquals("SuppliedAttributeId"); } + @Test + public void testIntEnum() { + final Property property = new Property( + "intEnumProperty", + "getIntEnumProperty", + Property.Type.INT_ENUM); + + property.setIntEnumEntries(Arrays.asList( + new IntEnumEntry("THREE", 3), + new IntEnumEntry("TWO", 2), + new IntEnumEntry("ONE", 1))); + + mModel.putProperty(property); + + assertGeneratedFileEquals("IntEnum"); + } + + @Test + public void testIntFlag() { + final Property property = new Property( + "intFlag", + "getIntFlag", + Property.Type.INT_FLAG); + + property.setAttributeIdInferrableFromR(false); + property.setIntFlagEntries(Arrays.asList( + new IntFlagEntry("TURBO", 0x1, 0x3), + new IntFlagEntry("OVERDRIVE", 0x2, 0x3), + new IntFlagEntry("WARP", 0x4) + )); + + mModel.putProperty(property); + + assertGeneratedFileEquals("IntFlag"); + } + private Property addProperty(String name, String getter, Property.Type type) { final Property property = new Property(name, getter, type); mModel.putProperty(property); diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt new file mode 100644 index 000000000000..764aa8bfbd63 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt @@ -0,0 +1,45 @@ +package com.android.node; + +import android.R; +import android.view.inspector.InspectionCompanion; +import android.view.inspector.IntEnumMapping; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link TestNode}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + /** + * Property ID of {@code intEnumProperty}. + */ + private int mIntEnumPropertyId; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mIntEnumPropertyId = propertyMapper.mapIntEnum("intEnumProperty", R.attr.intEnumProperty, + new IntEnumMapping.Builder() + .addValue("ONE", 1) + .addValue("TWO", 2) + .addValue("THREE", 3) + .build()); + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestNode node, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + propertyReader.readIntEnum(mIntEnumPropertyId, node.getIntEnumProperty()); + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt new file mode 100644 index 000000000000..75f281314965 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt @@ -0,0 +1,43 @@ +package com.android.node; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.IntFlagMapping; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link TestNode}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + /** + * Property ID of {@code intFlag}. + */ + private int mIntFlagId; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mIntFlagId = propertyMapper.mapIntFlag("intFlag", 0, new IntFlagMapping.Builder() + .addFlag("OVERDRIVE", 0x00000002, 0x00000003) + .addFlag("TURBO", 0x00000001, 0x00000003) + .addFlag("WARP", 0x00000004) + .build()); + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestNode node, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + propertyReader.readIntFlag(mIntFlagId, node.getIntFlag()); + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt index 2fc242c6cf4c..0cac462fba51 100644 --- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt @@ -1,4 +1,4 @@ -package com.android.inspectable; +package com.android.node; import android.view.inspector.InspectionCompanion; import android.view.inspector.PropertyMapper; @@ -23,7 +23,7 @@ public final class Outer$Inner$$InspectionCompanion implements InspectionCompani } @Override - public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) { + public void readProperties(Outer.Inner node, PropertyReader propertyReader) { if (!mPropertiesMapped) { throw new InspectionCompanion.UninitializedPropertyMapException(); } diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt index 23d0f7807aa5..ce0f867d5332 100644 --- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt @@ -1,4 +1,4 @@ -package com.android.inspectable; +package com.android.node; import android.view.inspector.InspectionCompanion; import android.view.inspector.PropertyMapper; @@ -6,12 +6,12 @@ import android.view.inspector.PropertyReader; import java.lang.Override; /** - * Inspection companion for {@link TestInspectable}. + * Inspection companion for {@link TestNode}. * * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. */ -public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { /** * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. */ @@ -29,10 +29,10 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom } @Override - public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + public void readProperties(TestNode node, PropertyReader propertyReader) { if (!mPropertiesMapped) { throw new InspectionCompanion.UninitializedPropertyMapException(); } - propertyReader.readInt(mNoAttributePropertyId, inspectable.getNoAttributeProperty()); + propertyReader.readInt(mNoAttributePropertyId, node.getNoAttributeProperty()); } } diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt index 11425482ce94..f7357fece2bf 100644 --- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt @@ -1,4 +1,4 @@ -package com.android.inspectable; +package com.android.node; import android.view.inspector.InspectionCompanion; import android.view.inspector.PropertyMapper; @@ -7,12 +7,12 @@ import java.lang.Override; import java.lang.String; /** - * Inspection companion for {@link TestInspectable}. + * Inspection companion for {@link TestNode}. * * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. */ -public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { /** * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. */ @@ -24,7 +24,7 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom } @Override - public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + public void readProperties(TestNode node, PropertyReader propertyReader) { if (!mPropertiesMapped) { throw new InspectionCompanion.UninitializedPropertyMapException(); } diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt index 57eb08041131..dfc1bce8afcc 100644 --- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt @@ -1,4 +1,4 @@ -package com.android.inspectable; +package com.android.node; import android.R; import android.view.inspector.InspectionCompanion; @@ -7,12 +7,12 @@ import android.view.inspector.PropertyReader; import java.lang.Override; /** - * Inspection companion for {@link TestInspectable}. + * Inspection companion for {@link TestNode}. * * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. */ -public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { /** * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. */ @@ -90,20 +90,20 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom } @Override - public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + public void readProperties(TestNode node, PropertyReader propertyReader) { if (!mPropertiesMapped) { throw new InspectionCompanion.UninitializedPropertyMapException(); } - propertyReader.readBoolean(mBooleanId, inspectable.getBoolean()); - propertyReader.readByte(mByteId, inspectable.getByte()); - propertyReader.readChar(mCharId, inspectable.getChar()); - propertyReader.readColor(mColorId, inspectable.getColor()); - propertyReader.readDouble(mDoubleId, inspectable.getDouble()); - propertyReader.readFloat(mFloatId, inspectable.getFloat()); - propertyReader.readGravity(mGravityId, inspectable.getGravity()); - propertyReader.readInt(mIntId, inspectable.getInt()); - propertyReader.readLong(mLongId, inspectable.getLong()); - propertyReader.readObject(mObjectId, inspectable.getObject()); - propertyReader.readShort(mShortId, inspectable.getShort()); + propertyReader.readBoolean(mBooleanId, node.getBoolean()); + propertyReader.readByte(mByteId, node.getByte()); + propertyReader.readChar(mCharId, node.getChar()); + propertyReader.readColor(mColorId, node.getColor()); + propertyReader.readDouble(mDoubleId, node.getDouble()); + propertyReader.readFloat(mFloatId, node.getFloat()); + propertyReader.readGravity(mGravityId, node.getGravity()); + propertyReader.readInt(mIntId, node.getInt()); + propertyReader.readLong(mLongId, node.getLong()); + propertyReader.readObject(mObjectId, node.getObject()); + propertyReader.readShort(mShortId, node.getShort()); } } diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt index 6b6ce2157481..d72cdd533205 100644 --- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt @@ -1,4 +1,4 @@ -package com.android.inspectable; +package com.android.node; import android.view.inspector.InspectionCompanion; import android.view.inspector.PropertyMapper; @@ -6,12 +6,12 @@ import android.view.inspector.PropertyReader; import java.lang.Override; /** - * Inspection companion for {@link TestInspectable}. + * Inspection companion for {@link TestNode}. * * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. */ -public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { +public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> { /** * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. */ @@ -30,10 +30,10 @@ public final class TestInspectable$$InspectionCompanion implements InspectionCom } @Override - public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + public void readProperties(TestNode node, PropertyReader propertyReader) { if (!mPropertiesMapped) { throw new InspectionCompanion.UninitializedPropertyMapException(); } - propertyReader.readInt(mSuppliedAttributePropertyId, inspectable.getSuppliedAttributeProperty()); + propertyReader.readInt(mSuppliedAttributePropertyId, node.getSuppliedAttributeProperty()); } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 066823931832..e5733edd9803 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1221,7 +1221,6 @@ public class WifiManager { * @param scanResults a list of scanResult that represents the BSSID * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}). - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ @SystemApi @@ -1264,7 +1263,6 @@ public class WifiManager { * * @param scanResults a list of ScanResult * @return Map that consists {@link OsuProvider} and a list of matching {@link ScanResult} - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ @SystemApi @@ -1291,7 +1289,6 @@ public class WifiManager { * * @param osuProviders a set of {@link OsuProvider} * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}. - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ @SystemApi @@ -1720,8 +1717,8 @@ public class WifiManager { * FQDN, the new configuration will replace the existing configuration. * * @param config The Passpoint configuration to be added - * @throws IllegalArgumentException if configuration is invalid - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. + * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on + * the device. */ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { @@ -1737,8 +1734,8 @@ public class WifiManager { * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name). * * @param fqdn The FQDN of the Passpoint configuration to be removed - * @throws IllegalArgumentException if no configuration is associated with the given FQDN. - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. + * @throws IllegalArgumentException if no configuration is associated with the given FQDN or + * Passpoint is not enabled on the device. * @deprecated This is no longer supported. */ @Deprecated @@ -1762,7 +1759,6 @@ public class WifiManager { * An empty list will be returned when no configurations are installed. * * @return A list of {@link PasspointConfiguration} - * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @deprecated This is no longer supported. */ @Deprecated |