diff options
486 files changed, 18736 insertions, 6522 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/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java index a7a81f2d20bb..767434d0831c 100644 --- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java +++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java @@ -91,7 +91,7 @@ public class TextClassifierPerfTest { private static ConversationActions.Request createConversationActionsRequest(CharSequence text) { ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(text) .build(); return new ConversationActions.Request.Builder(Collections.singletonList(message)) diff --git a/api/current.txt b/api/current.txt index b5bdabe6e258..83b4bb1ffd8d 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(); @@ -5466,9 +5467,11 @@ package android.app { public static final class Notification.BubbleMetadata implements android.os.Parcelable { method public int describeContents(); + method public boolean getAutoExpandBubble(); method public int getDesiredHeight(); method public android.graphics.drawable.Icon getIcon(); method public android.app.PendingIntent getIntent(); + method public boolean getSuppressInitialNotification(); method public CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR; @@ -5477,9 +5480,11 @@ package android.app { public static class Notification.BubbleMetadata.Builder { ctor public Notification.BubbleMetadata.Builder(); method public android.app.Notification.BubbleMetadata build(); + method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean); method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int); method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon); method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent); + method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean); method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence); } @@ -5772,6 +5777,7 @@ package android.app { method public String addAutomaticZenRule(android.app.AutomaticZenRule); method public boolean areBubblesAllowed(); method public boolean areNotificationsEnabled(); + method public boolean areNotificationsPaused(); method public boolean canNotifyAsPackage(String); method public void cancel(int); method public void cancel(String, int); @@ -11431,6 +11437,7 @@ package android.content.pm { method public int getSessionId(); method public long getSize(); method public int getStagedSessionErrorCode(); + method public String getStagedSessionErrorMessage(); method public boolean isActive(); method public boolean isMultiPackage(); method public boolean isSealed(); @@ -12507,6 +12514,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); @@ -12530,6 +12538,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 @@ -13942,8 +13951,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); @@ -14162,6 +14169,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 @@ -16166,6 +16174,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; @@ -16493,6 +16502,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); @@ -23517,6 +23527,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); @@ -25950,7 +25962,6 @@ package android.media { method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); method public final void removeSession(@NonNull android.media.MediaSession2); - field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; } public static class MediaSession2Service.MediaNotification { @@ -27473,6 +27484,7 @@ package android.media.session { method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens(); method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo); method public void notifySession2Created(@NonNull android.media.Session2Token); + method public void notifySession2Destroyed(@NonNull android.media.Session2Token); method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); } @@ -27675,6 +27687,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"; @@ -34402,8 +34415,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(); @@ -38639,6 +38656,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"; @@ -38672,6 +38690,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"; @@ -38746,6 +38765,7 @@ package android.provider { public static final class Settings.Panel { field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final String ACTION_NFC = "android.settings.panel.action.NFC"; field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; } @@ -41401,9 +41421,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"; } @@ -44658,12 +44678,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); @@ -44726,6 +44748,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); @@ -44995,6 +45022,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"; @@ -45033,6 +45061,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"; @@ -48564,6 +48593,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); @@ -53342,8 +53372,8 @@ package android.view.textclassifier { method @Nullable public CharSequence getText(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; - field public static final android.app.Person PERSON_USER_LOCAL; - field public static final android.app.Person PERSON_USER_REMOTE; + field public static final android.app.Person PERSON_USER_OTHERS; + field public static final android.app.Person PERSON_USER_SELF; } public static final class ConversationActions.Message.Builder { @@ -55927,10 +55957,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 { @@ -62382,20 +62412,20 @@ package java.nio { method public abstract Object array(); method public abstract int arrayOffset(); method public final int capacity(); - method public final java.nio.Buffer clear(); - method public final java.nio.Buffer flip(); + method public java.nio.Buffer clear(); + method public java.nio.Buffer flip(); method public abstract boolean hasArray(); method public final boolean hasRemaining(); method public abstract boolean isDirect(); method public abstract boolean isReadOnly(); method public final int limit(); - method public final java.nio.Buffer limit(int); - method public final java.nio.Buffer mark(); + method public java.nio.Buffer limit(int); + method public java.nio.Buffer mark(); method public final int position(); - method public final java.nio.Buffer position(int); + method public java.nio.Buffer position(int); method public final int remaining(); - method public final java.nio.Buffer reset(); - method public final java.nio.Buffer rewind(); + method public java.nio.Buffer reset(); + method public java.nio.Buffer rewind(); } public class BufferOverflowException extends java.lang.RuntimeException { diff --git a/api/system-current.txt b/api/system-current.txt index 213aedc4b44d..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(); } @@ -3414,6 +3418,15 @@ package android.media { method public void stop(); } + public final class Session2Token implements android.os.Parcelable { + ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle); + method public void destroy(); + method @NonNull public android.os.Bundle getExtras(); + method public int getPid(); + method public boolean isDestroyed(); + field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + public static class SubtitleData.Builder { ctor public SubtitleData.Builder(); ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData); @@ -4092,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); @@ -4240,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(); @@ -4265,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 { @@ -4685,7 +4742,6 @@ package android.net.wifi { method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); @@ -4695,7 +4751,6 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int); field public static final int CHANGE_REASON_ADDED = 0; // 0x0 field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2 @@ -4732,19 +4787,6 @@ package android.net.wifi { method public void onSuccess(); } - public static interface WifiManager.NetworkRequestMatchCallback { - method public void onAbort(); - method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>); - method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback); - method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration); - method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration); - } - - public static interface WifiManager.NetworkRequestUserSelectionCallback { - method public void reject(); - method public void select(@NonNull android.net.wifi.WifiConfiguration); - } - public static interface WifiManager.WifiUsabilityStatsListener { method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry); } @@ -5048,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"; } @@ -5065,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; } @@ -5539,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"; } @@ -5671,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"; @@ -5679,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 { @@ -7855,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(); @@ -7866,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; } @@ -7882,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(); @@ -8250,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(); @@ -8267,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(); @@ -8276,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); @@ -8712,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..812a2f2a76ae 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -24,14 +24,18 @@ import "frameworks/base/cmds/statsd/src/atom_field_options.proto"; import "frameworks/base/core/proto/android/app/enums.proto"; import "frameworks/base/core/proto/android/app/settings_enums.proto"; import "frameworks/base/core/proto/android/app/job/enums.proto"; +import "frameworks/base/core/proto/android/bluetooth/a2dp/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto"; +import "frameworks/base/core/proto/android/bluetooth/smp/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 +150,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 +167,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 +213,31 @@ 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; + BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151; + BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152; + BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = 153; + BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = 154; + BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = 155; + BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = 156; + BluetoothDeviceRssiReported bluetooth_device_rssi_reported = 157; + BluetoothDeviceFailedContactCounterReported bluetooth_device_failed_contact_counter_reported = 158; + BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = 159; + BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = 160; + BluetoothQualityReportReported bluetooth_quality_report_reported = 161; + BluetoothManufacturerInfoReported bluetooth_device_info_reported = 162; + BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = 163; + BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = 164; + BluetoothBondStateChanged bluetooth_bond_state_changed = 165; + BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166; + BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167; } // 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 +270,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 +285,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. @@ -1400,6 +1428,27 @@ message BluetoothScoConnectionStateChanged { optional android.bluetooth.hfp.ScoCodec codec = 3; } +/** + * Logged when active device of a profile changes + * + * Logged from: + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + * packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java + * packages/apps/Bluetooth/src/com/android/bluetooth/hearingaid/HearingAidService.java + */ +message BluetoothActiveDeviceChanged { + // The profile whose active device has changed. Eg. A2DP, HEADSET, HEARING_AID + // From android.bluetooth.BluetoothProfile + optional int32 bt_profile = 1; + // An identifier that can be used to match events for this new active device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if there is no active device for this profile + optional bytes obfuscated_id = 2 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + // Logs when there is an event affecting Bluetooth device's link layer connection. // - This event is triggered when there is a related HCI command or event // - Users of this metrics can deduce Bluetooth device's connection state from these events @@ -1483,6 +1532,535 @@ 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 there is a change in Bluetooth A2DP playback state + * + * Logged from: + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpPlaybackStateChanged { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Current playback state + // Default: PLAYBACK_STATE_UNKNOWN + optional android.bluetooth.a2dp.PlaybackStateEnum playback_state = 2; + // Current audio coding mode + // Default: AUDIO_CODING_MODE_UNKNOWN + optional android.bluetooth.a2dp.AudioCodingModeEnum audio_coding_mode = 3; +} + +/** + * Logs when there is a change in A2DP codec config for a particular remote device + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpCodecConfigChanged { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig + // Default SOURCE_CODEC_TYPE_INVALID + optional int32 codec_type = 2; + // Codec priroity, the higher the more preferred, -1 for disabled + // Default: CODEC_PRIORITY_DEFAULT + optional int32 codec_priority = 3; + // Sample rate in Hz as defined by various SAMPLE_RATE_* constants in BluetoothCodecConfig + // Default: SAMPLE_RATE_NONE + optional int32 sample_rate = 4; + // Bits per sample as defined by various BITS_PER_SAMPLE_* constants in BluetoothCodecConfig + // Default: BITS_PER_SAMPLE_NONE + optional int32 bits_per_sample = 5; + // Channel mode as defined by various CHANNEL_MODE_* constants in BluetoothCodecConfig + // Default: CHANNEL_MODE_NONE + optional int32 channel_mode = 6; + // Codec specific values + // Default 0 + optional int64 codec_specific_1 = 7; + optional int64 codec_specific_2 = 8; + optional int64 codec_specific_3 = 9; + optional int64 codec_specific_4 = 10; +} + +/** + * Logs when there is a change in selectable A2DP codec capability for a paricular remote device + * Each codec's capability is logged separately due to statsd restriction + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java + * packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java + */ +message BluetoothA2dpCodecCapabilityChanged { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig + // Default SOURCE_CODEC_TYPE_INVALID + optional int32 codec_type = 2; + // Codec priroity, the higher the more preferred, -1 for disabled + // Default: CODEC_PRIORITY_DEFAULT + optional int32 codec_priority = 3; + // A bit field of supported sample rates as defined by various SAMPLE_RATE_* constants + // in BluetoothCodecConfig + // Default: empty and SAMPLE_RATE_NONE for individual item + optional int32 sample_rate = 4; + // A bit field of supported bits per sample as defined by various BITS_PER_SAMPLE_* constants + // in BluetoothCodecConfig + // Default: empty and BITS_PER_SAMPLE_NONE for individual item + optional int32 bits_per_sample = 5; + // A bit field of supported channel mode as defined by various CHANNEL_MODE_* constants in + // BluetoothCodecConfig + // Default: empty and CHANNEL_MODE_NONE for individual item + optional int32 channel_mode = 6; + // Codec specific values + // Default 0 + optional int64 codec_specific_1 = 7; + optional int64 codec_specific_2 = 8; + optional int64 codec_specific_3 = 9; + optional int64 codec_specific_4 = 10; +} + +/** + * Logs when A2DP failed to read from PCM source. + * This typically happens when audio HAL cannot supply A2DP with data fast enough for encoding. + * + * Logged from: + * system/bt + */ +message BluetoothA2dpAudioUnderrunReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Encoding interval in nanoseconds + // Default: 0 + optional int64 encoding_interval_nanos = 2; + // Number of bytes of PCM data that could not be read from the source + // Default: 0 + optional int32 num_missing_pcm_bytes = 3; +} + +/** + * Logs when A2DP failed send encoded data to the remote device fast enough such that the transmit + * buffer queue is full and we have to drop data + * + * Logged from: + * system/bt + */ +message BluetoothA2dpAudioOverrunReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Encoding interval in nanoseconds + // Default: 0 + optional int64 encoding_interval_nanos = 2; + // Number of buffers dropped in this event + // Each buffer is encoded in one encoding interval and consists of multiple encoded frames + // Default: 0 + optional int32 num_dropped_buffers = 3; + // Number of encoded buffers dropped in this event + // Default 0 + optional int32 num_dropped_encoded_frames = 4; + // Number of encoded bytes dropped in this event + // Default: 0 + optional int32 num_dropped_encoded_bytes = 5; +} + +/** + * Logs when we receive reports regarding a device's RSSI value + * + * Logged from: + * system/bt + */ +message BluetoothDeviceRssiReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 3; + // BR/EDR + // Range: -128 ≤ N ≤ 127 (signed integer) + // Units: dB + // LE: + // Range: -127 to 20, 127 (signed integer) + // Units: dBm + // Invalid when an out of range value is reported + optional int32 rssi = 4; +} + +/** + * Logs when we receive reports regarding how many consecutive failed contacts for a connection + * + * Logged from: + * system/bt + */ +message BluetoothDeviceFailedContactCounterReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum cmd_status = 3; + // Number of consecutive failed contacts for a connection corresponding to the Handle + // Range: uint16_t, 0-0xFFFF + // Default: 0xFFFFF + optional int32 failed_contact_counter = 4; +} + +/** + * Logs when we receive reports regarding the tranmit power level used for a specific connection + * + * Logged from: + * system/bt + */ +message BluetoothDeviceTxPowerLevelReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command status code if this is triggered by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 3; + // Range: -30 ≤ N ≤ 20 + // Units: dBm + // Invalid when an out of range value is reported + optional int32 transmit_power_level = 4; +} + +/** + * Logs when Bluetooth controller failed to reply with command status within a timeout period after + * receiving an HCI command from the host + * + * Logged from: system/bt + */ +message BluetoothHciTimeoutReported { + // HCI command associated with this event + // Default: CMD_UNKNOWN + optional android.bluetooth.hci.CommandEnum hci_command = 1; +} + +/** + * Logs when we receive Bluetooth Link Quality Report event from the controller + * See Android Bluetooth HCI specification for more details + * + * Note: all count and bytes field are counted since last event + * + * Logged from: system/bt + */ +message BluetoothQualityReportReported { + // Quality report ID + // Original type: uint8_t + // Default: BQR_ID_UNKNOWN + optional android.bluetooth.hci.BqrIdEnum quality_report_id = 1; + // Packet type of the connection + // Original type: uint8_t + // Default: BQR_PACKET_TYPE_UNKNOWN + optional android.bluetooth.hci.BqrPacketTypeEnum packet_types = 2; + // Connection handle of the connection + // Original type: uint16_t + optional int32 connection_handle = 3; + // Performing Role for the connection + // Original type: uint8_t + optional int32 connection_role = 4; + // Current Transmit Power Level for the connection. This value is the same as the controller's + // response to the HCI_Read_Transmit_Power_Level HCI command + // Original type: uint8_t + optional int32 tx_power_level = 5; + // Received Signal Strength Indication (RSSI) value for the connection. This value is an + // absolute receiver signal strength value + // Original type: int8_t + optional int32 rssi = 6; + // Signal-to-Noise Ratio (SNR) value for the connection. It is the average SNR of all the + // channels used by the link currently + // Original type: uint8_t + optional int32 snr = 7; + // Indicates the number of unused channels in AFH_channel_map + // Original type: uint8_t + optional int32 unused_afh_channel_count = 8; + // Indicates the number of the channels which are interfered and quality is bad but are still + // selected for AFH + // Original type: uint8_t + optional int32 afh_select_unideal_channel_count = 9; + // Current Link Supervision Timeout Setting + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint16_t + optional int32 lsto = 10; + // Piconet Clock for the specified Connection_Handle. This value is the same as the controller's + // response to HCI_Read_Clock HCI command with the parameter "Which_Clock" of + // 0x01 (Piconet Clock) + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 connection_piconet_clock = 11; + // The count of retransmission + // Original type: uint32_t + optional int64 retransmission_count = 12; + // The count of no RX + // Original type: uint32_t + optional int64 no_rx_count = 13; + // The count of NAK (Negative Acknowledge) + // Original type: uint32_t + optional int64 nak_count = 14; + // Controller timestamp of last TX ACK + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 last_tx_ack_timestamp = 15; + // The count of Flow-off (STOP) + // Original type: uint32_t + optional int64 flow_off_count = 16; + // Controller timestamp of last Flow-on (GO) + // Unit: N * 0.3125 ms (1 Bluetooth Clock) + // Original type: uint32_t + optional int64 last_flow_on_timestamp = 17; + // Buffer overflow count (how many bytes of TX data are dropped) since the last event + // Original type: uint32_t + optional int64 buffer_overflow_bytes = 18; + // Buffer underflow count (in byte) since last event + // Original type: uint32_t + optional int64 buffer_underflow_bytes = 19; +} + +/** + * Logs when a Bluetooth device's manufacturer information is learnt by the Bluetooth stack + * + * Notes: + * - Each event can be partially filled as we might learn different pieces of device + * information at different time + * - Multiple device info events can be combined to give more complete picture + * - When multiple device info events tries to describe the same information, the + * later one wins + * + * Logged from: + * packages/apps/Bluetooth + */ +message BluetoothManufacturerInfoReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Where is this device info obtained from + optional android.bluetooth.DeviceInfoSrcEnum source_type = 2; + // Name of the data source + // For EXTERNAL: package name of the data source + // For INTERNAL: null for general case, component name otherwise + optional string source_name = 3; + // Name of the manufacturer of this device + optional string manufacturer = 4; + // Model of this device + optional string model = 5; + // Hardware version of this device + optional string hardware_version = 6; + // Software version of this device + optional string software_version = 7; +} + +/** + * Logs when we receive Bluetooth Read Remote Version Information Complete Event from the remote + * device, as documented by the Bluetooth Core HCI specification + * Reference: https://www.bluetooth.com/specifications/bluetooth-core-specification + * Vol 2, Part E, Page 1118 + * + * Logged from: + * system/bt + */ +message BluetoothRemoteVersionInfoReported { + // Connection handle of the connection + // Original type: uint16_t + optional int32 connection_handle = 1; + // HCI command status code + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum hci_status = 2; + // 1 byte Version of current LMP in the remote controller + optional int32 lmp_version = 3; + // 2 bytes LMP manufacturer code of the remote controller + // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers + optional int32 lmp_manufacturer_code = 4; + // 4 bytes subversion of the LMP in the remote controller + optional int32 lmp_subversion = 5; +} + +/** + * Logs when certain Bluetooth SDP attributes are discovered + * Constant definitions are from: + * https://www.bluetooth.com/specifications/assigned-numbers/service-discovery + * + * Current logged attributes: + * - BluetoothProfileDescriptorList + * - Supported Features Bitmask + * + * Logged from: + * system/bt + */ +message BluetoothSdpAttributeReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Short form UUIDs used to identify Bluetooth protocols, profiles, and service classes + // Original type: uint16_t + optional int32 protocol_uuid = 2; + // Short form UUIDs used to identify Bluetooth SDP attribute types + // Original type: uint16_t + optional int32 attribute_id = 3; + // Attribute value for the particular attribute + optional bytes attribute_value = 4 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Logs when bond state of a Bluetooth device changes + * + * Logged from: + * frameworks/base/core/java/android/bluetooth/BluetoothDevice.java + * packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java + */ +message BluetoothBondStateChanged { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Preferred transport type to remote dual mode device + // Default: TRANSPORT_AUTO means no preference + optional android.bluetooth.TransportTypeEnum transport = 2; + // The type of this Bluetooth device (Classic, LE, or Dual mode) + // Default: UNKNOWN + optional android.bluetooth.DeviceTypeEnum type = 3; + // Current bond state (NONE, BONDING, BONDED) + // Default: BOND_STATE_UNKNOWN + optional android.bluetooth.BondStateEnum bond_state = 4; + // Bonding sub state + // Default: BOND_SUB_STATE_UNKNOWN + optional android.bluetooth.BondSubStateEnum bonding_sub_state = 5; + // Unbond Reason + // Default: UNBOND_REASON_UNKNOWN + optional android.bluetooth.UnbondReasonEnum unbond_reason = 6; +} + +/** + * Logs there is an event related Bluetooth classic pairing + * + * Logged from: + * system/bt + */ +message BluetoothClassicPairingEventReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Connection handle of this connection if available + // Range: 0x0000 - 0x0EFF (12 bits) + // Default: 0xFFFF if the handle is unknown + optional int32 connection_handle = 2; + // HCI command associated with this event + // Default: CMD_UNKNOWN + optional android.bluetooth.hci.CommandEnum hci_cmd = 3; + // HCI event associated with this event + // Default: EVT_UNKNOWN + optional android.bluetooth.hci.EventEnum hci_event = 4; + // HCI command status code if this is triggerred by hci_cmd + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum cmd_status = 5; + // HCI reason code associated with this event + // Default: STATUS_UNKNOWN + optional android.bluetooth.hci.StatusEnum reason_code = 6; +} + +/** + * Logs when there is an event related to Bluetooth Security Manager Protocol (SMP) + * + * Logged from: + * system/bt + */ +message BluetoothSmpPairingEventReported { + // An identifier that can be used to match events for this device. + // Currently, this is a salted hash of the MAC address of this Bluetooth device. + // Salt: Randomly generated 256 bit value + // Hash algorithm: HMAC-SHA256 + // Size: 32 byte + // Default: null or empty if the device identifier is not known + optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES]; + // SMP command sent or received over L2CAP + // Default: CMD_UNKNOWN + optional android.bluetooth.smp.CommandEnum smp_command = 2; + // Whether this command is sent or received + // Default: DIRECTION_UNKNOWN + optional android.bluetooth.DirectionEnum direction = 3; + // SMP failure reason code + // Default: PAIRING_FAIL_REASON_DEFAULT + optional android.bluetooth.smp.PairingFailReasonEnum smp_fail_reason = 4; +} /** * Logs when something is plugged into or removed from the USB-C connector. @@ -2351,58 +2929,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 +3091,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 +3960,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 +5168,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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 199c1338c50c..9ea39f7606a8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -94,6 +94,7 @@ interface INotificationManager boolean areChannelsBypassingDnd(); int getAppsBypassingDndCount(int uid); ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId); + boolean isPackagePaused(String pkg); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. 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/Notification.java b/core/java/android/app/Notification.java index b405d0ccc3d1..7c550d4332fe 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -84,7 +84,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -8403,6 +8402,30 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * in its expanded state, with the contents of {@link #getIntent()} in a floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * <b>without</b> the associated notification in the notification shade. Subsequent update + * notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002; private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) { mPendingIntent = intent; @@ -8416,6 +8439,7 @@ public class Notification implements Parcelable mTitle = in.readCharSequence(); mIcon = Icon.CREATOR.createFromParcel(in); mDesiredHeight = in.readInt(); + mFlags = in.readInt(); } /** @@ -8448,6 +8472,24 @@ public class Notification implements Parcelable return mDesiredHeight; } + /** + * @return whether this bubble should auto expand when it is posted. + * + * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) + */ + public boolean getAutoExpandBubble() { + return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; + } + + /** + * @return whether this bubble should suppress the initial notification when it is posted. + * + * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean) + */ + public boolean getSuppressInitialNotification() { + return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0; + } + public static final Parcelable.Creator<BubbleMetadata> CREATOR = new Parcelable.Creator<BubbleMetadata>() { @@ -8473,6 +8515,11 @@ public class Notification implements Parcelable out.writeCharSequence(mTitle); mIcon.writeToParcel(out, 0); out.writeInt(mDesiredHeight); + out.writeInt(mFlags); + } + + private void setFlags(int flags) { + mFlags = flags; } /** @@ -8484,6 +8531,7 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; /** * Constructs a new builder object. @@ -8543,6 +8591,39 @@ public class Notification implements Parcelable } /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted in its expanded state, with the contents of {@link #getIntent()} in a + * floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { + setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); + return this; + } + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted <b>without</b> the associated notification in the notification shade. + * Subsequent update notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setSuppressInitialNotification( + boolean shouldSupressNotif) { + setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif); + return this; + } + + /** * Creates the {@link BubbleMetadata} defined by this builder. * <p>Will throw {@link IllegalStateException} if required fields have not been set * on this builder.</p> @@ -8557,7 +8638,22 @@ public class Notification implements Parcelable if (mIcon == null) { throw new IllegalStateException("Must supply an icon for the bubble"); } - return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight); + BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon, + mDesiredHeight); + data.setFlags(mFlags); + return data; + } + + /** + * @hide + */ + public BubbleMetadata.Builder setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + return this; } } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index c4b4b4070ce7..621f134dc68e 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1094,6 +1094,22 @@ public class NotificationManager { } /** + * Returns whether notifications from this package are temporarily hidden. This + * could be done because the package was marked as distracting to the user via + * {@code PackageManager#setDistractingPackageRestrictions(String[], int)} or because the + * package is {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, SuspendDialogInfo) suspended}. + */ + public boolean areNotificationsPaused() { + INotificationManager service = getService(); + try { + return service.isPackagePaused(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Checks the ability to modify notification do not disturb policy for the calling package. * * <p> 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/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 3d3c03ae3daa..43ce521fa9f3 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -287,19 +287,14 @@ public abstract class UsageStatsManagerInternal { /** A class which is used to share the usage limit data for an app or a group of apps. */ public static class AppUsageLimitData { - private final boolean mGroupLimit; private final long mTotalUsageLimit; private final long mUsageRemaining; - public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) { - this.mGroupLimit = groupLimit; + public AppUsageLimitData(long totalUsageLimit, long usageRemaining) { this.mTotalUsageLimit = totalUsageLimit; this.mUsageRemaining = usageRemaining; } - public boolean isGroupLimit() { - return mGroupLimit; - } public long getTotalUsageLimit() { return mTotalUsageLimit; } 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 edd765b05415..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 @@ -9976,9 +9975,21 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ + public void writeToProto(ProtoOutputStream proto) { + // Same input parameters that toString() gives to toShortString(). + writeToProtoWithoutFieldId(proto, true, true, true, false); + } + + /** @hide */ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp, boolean extras, boolean clip) { long token = proto.start(fieldId); + writeToProtoWithoutFieldId(proto, secure, comp, extras, clip); + proto.end(token); + } + + private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp, + boolean extras, boolean clip) { if (mAction != null) { proto.write(IntentProto.ACTION, mAction); } @@ -10023,7 +10034,6 @@ public class Intent implements Parcelable, Cloneable { if (mSelector != null) { proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip)); } - proto.end(token); } /** diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index b7366f1bbafc..e897b917fcc2 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -18,7 +18,6 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -77,18 +76,6 @@ public class CrossProfileApps { } /** - * @deprecated use {@link #startActivity(ComponentName, UserHandle)} instead. - * - * @removed - * @hide - */ - @Deprecated - @UnsupportedAppUsage - public void startAnyActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) { - startActivity(component, targetUser); - } - - /** * Starts the specified activity of the caller package in the specified profile. Unlike * {@link #startMainActivity}, this can start any activity of the caller package, not just * the main activity. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 89630e15972e..36bb5d49666d 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1658,35 +1658,23 @@ public class LauncherApps { * A class that encapsulates information about the usage limit set for an app or * a group of apps. * - * <p>The launcher can query specifics about the usage limit such as if it is a group limit, - * how much usage time the limit has, and how much of the total usage time is remaining - * via the APIs available in this class. + * <p>The launcher can query specifics about the usage limit such as how much usage time + * the limit has and how much of the total usage time is remaining via the APIs available + * in this class. * * @see #getAppUsageLimit(String, UserHandle) */ public static final class AppUsageLimit implements Parcelable { - private final boolean mGroupLimit; private final long mTotalUsageLimit; private final long mUsageRemaining; /** @hide */ - public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) { - this.mGroupLimit = groupLimit; + public AppUsageLimit(long totalUsageLimit, long usageRemaining) { this.mTotalUsageLimit = totalUsageLimit; this.mUsageRemaining = usageRemaining; } /** - * Returns whether this limit refers to a group of apps. - * - * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise. - * @hide - */ - public boolean isGroupLimit() { - return mGroupLimit; - } - - /** * Returns the total usage limit in milliseconds set for an app or a group of apps. * * @return the total usage limit in milliseconds @@ -1706,7 +1694,6 @@ public class LauncherApps { } private AppUsageLimit(Parcel source) { - mGroupLimit = source.readBoolean(); mTotalUsageLimit = source.readLong(); mUsageRemaining = source.readLong(); } @@ -1730,7 +1717,6 @@ public class LauncherApps { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mGroupLimit); dest.writeLong(mTotalUsageLimit); dest.writeLong(mUsageRemaining); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 73b1f4e7e536..2dc014c45fad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1710,6 +1710,7 @@ public class PackageInstaller { /** {@hide} */ public boolean isSessionFailed; private int mStagedSessionErrorCode; + private String mStagedSessionErrorMessage; /** {@hide} */ @UnsupportedAppUsage @@ -1749,6 +1750,7 @@ public class PackageInstaller { isSessionReady = source.readBoolean(); isSessionFailed = source.readBoolean(); mStagedSessionErrorCode = source.readInt(); + mStagedSessionErrorMessage = source.readString(); } /** @@ -2066,9 +2068,19 @@ public class PackageInstaller { return mStagedSessionErrorCode; } + /** + * Text description of the error code returned by {@code getStagedSessionErrorCode}, or + * empty string if no error was encountered. + */ + public String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + /** {@hide} */ - public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) { + public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode, + String errorMessage) { mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; } @Override @@ -2106,6 +2118,7 @@ public class PackageInstaller { dest.writeBoolean(isSessionReady); dest.writeBoolean(isSessionFailed); dest.writeInt(mStagedSessionErrorCode); + dest.writeString(mStagedSessionErrorMessage); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/content/pm/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 d840e3c720cc..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"; } /** @@ -14603,6 +14685,17 @@ public final class Settings { "android.settings.panel.action.INTERNET_CONNECTIVITY"; /** + * Activity Action: Show a settings dialog containing NFC-related settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NFC = + "android.settings.panel.action.NFC"; + + /** * Activity Action: Show a settings dialog containing all volume streams. * <p> * Input: Nothing. diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 140336e93357..dce5d56e24a4 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -18,6 +18,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -289,6 +290,7 @@ public class VoicemailContract { * Path to the media content file. Internal only field. * @hide */ + @TestApi public static final String _DATA = "_data"; // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming diff --git a/core/java/android/service/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 2984ef6e8a21..db9351b030df 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -40,6 +40,7 @@ public class FeatureFlagUtils { public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled"; + public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled"; private static final Map<String, String> DEFAULT_FLAGS; private static final Set<String> OBSERVABLE_FLAGS; @@ -51,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"); @@ -60,6 +61,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SAFETY_HUB, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false"); + DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false"); OBSERVABLE_FLAGS = new HashSet<>(); OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED); diff --git a/core/java/android/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 dd6231d9c5b0..4f9ecd575326 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,17 +16,24 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import android.util.Property; import android.util.SparseArray; +import android.view.InsetsState.InternalInsetType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetType; -import android.view.InsetsState.InternalInsetType; import com.android.internal.annotations.VisibleForTesting; @@ -39,6 +46,41 @@ import java.util.ArrayList; */ public class InsetsController implements WindowInsetsController { + // TODO: Use animation scaling and more optimal duration. + private static final int ANIMATION_DURATION_MS = 400; + private static final int DIRECTION_NONE = 0; + private static final int DIRECTION_SHOW = 1; + private static final int DIRECTION_HIDE = 2; + @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) + private @interface AnimationDirection{} + + /** + * Translation animation evaluator. + */ + private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( + 0, + (int) (startValue.top + fraction * (endValue.top - startValue.top)), + 0, + (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + + /** + * Linear animation property + */ + private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> { + InsetsProperty() { + super(Insets.class, "Insets"); + } + + @Override + public Insets get(WindowInsetsAnimationController object) { + return object.getCurrentInsets(); + } + @Override + public void set(WindowInsetsAnimationController object, Insets value) { + object.changeInsets(value); + } + } + private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); @@ -58,6 +100,8 @@ public class InsetsController implements WindowInsetsController { private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); + private ObjectAnimator mAnimator; + private @AnimationDirection int mAnimationDirection; public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -122,7 +166,10 @@ public class InsetsController implements WindowInsetsController { public void onControlsChanged(InsetsSourceControl[] activeControls) { if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { - mTmpControlArray.put(activeControl.getType(), activeControl); + if (activeControl != null) { + // TODO(b/122982984): Figure out why it can be null. + mTmpControlArray.put(activeControl.getType(), activeControl); + } } } @@ -146,18 +193,40 @@ public class InsetsController implements WindowInsetsController { @Override public void show(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).show(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_HIDE) { + // Only one animator (with multiple InsetType) can run at a time. + // previous one should be cancelled for simplicity. + cancelExistingAnimation(); + } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) { + // no-op: already shown or animating in. + // TODO: When we have more than one types: handle specific case when + // show animation is going on, but the current type is not becoming visible. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, true /* show */); } @Override public void hide(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).hide(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_SHOW) { + cancelExistingAnimation(); + } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) { + // no-op: already hidden or animating out. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, false /* show */); } @Override @@ -195,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; } @@ -207,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() { @@ -226,6 +321,79 @@ public class InsetsController implements WindowInsetsController { } } + private void applyAnimation(@InsetType final int types, boolean show) { + if (types == 0) { + // nothing to animate. + return; + } + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + mAnimator = ObjectAnimator.ofObject( + controller, + new InsetsProperty(), + sEvaluator, + show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(), + show ? controller.getShownStateInsets() : controller.getHiddenStateInsets() + ); + mAnimator.setDuration(ANIMATION_DURATION_MS); + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + onAnimationFinish(); + } + + @Override + public void onAnimationEnd(Animator animation) { + onAnimationFinish(); + } + }); + mAnimator.start(); + } + + @Override + public void onCancelled() {} + + private void onAnimationFinish() { + mAnimationDirection = DIRECTION_NONE; + if (show) { + showOnAnimationEnd(types); + } else { + hideOnAnimationEnd(types); + } + } + }; + // TODO: Instead of clearing this here, properly wire up + // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. + mAnimationControls.clear(); + controlWindowInsetsAnimation(types, listener); + } + + private void hideOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).hide(); + } + } + + private void showOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).show(); + } + } + + /** + * Cancel on-going animation to show/hide {@link InsetType}. + */ + @VisibleForTesting + public void cancelExistingAnimation() { + mAnimationDirection = DIRECTION_NONE; + if (mAnimator != null) { + mAnimator.cancel(); + } + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 145b09763676..f48318cd7d0a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,8 +17,8 @@ package android.view; import android.annotation.Nullable; -import android.view.SurfaceControl.Transaction; import android.view.InsetsState.InternalInsetType; +import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; @@ -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. @@ -89,6 +99,11 @@ public class InsetsSourceConsumer { return true; } + @VisibleForTesting + public boolean isVisible() { + return mVisible; + } + private void setVisible(boolean visible) { if (mVisible == visible) { return; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 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/View.java b/core/java/android/view/View.java index f1dfc1c6072f..cd3decf4e981 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9067,35 +9067,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid=" + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + ", visible=" + (getVisibility() == VISIBLE) - + ": alreadyNotifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)); + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - final ViewStructure structure = session.newViewStructure(this); - onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; + + // The code below doesn't take much for a unique view, but it's called for all views + // the first time the view hiearchy is laid off, which could acccumulative delay the + // initial layout. Hence, we're postponing it to a later stage - it might still cost a + // lost frame (or more), but that jank cost would only happen after the 1st layout. + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + final ViewStructure structure = session.newViewStructure(this); + onProvideContentCaptureStructure(structure, /* flags= */ 0); + session.notifyViewAppeared(structure); + }, /* token= */ null); } else { if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0 || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) { if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { - Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this - + ": notifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid=" + + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + + ", visible=" + (getVisibility() == VISIBLE) + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - session.notifyViewDisappeared(getAutofillId()); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, + () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null); } } 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/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index fdc34b3f68d0..4d917a1b1968 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -115,7 +115,7 @@ public final class ActionsSuggestionsHelper { private int mNextUserId = FIRST_NON_LOCAL_USER; private int encode(Person person) { - if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) { + if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) { return USER_LOCAL; } Integer result = mMapping.get(person); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index f7c1a2640dc5..502181f633b6 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -109,9 +109,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_LOCAL = + public static final Person PERSON_USER_SELF = new Person.Builder() - .setKey("text-classifier-conversation-actions-local-user") + .setKey("text-classifier-conversation-actions-user-self") .build(); /** @@ -123,9 +123,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_REMOTE = + public static final Person PERSON_USER_OTHERS = new Person.Builder() - .setKey("text-classifier-conversation-actions-remote-user") + .setKey("text-classifier-conversation-actions-user-others") .build(); @Nullable @@ -235,10 +235,10 @@ public final class ConversationActions implements Parcelable { /** * Constructs a builder. * - * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * @param author the person that composed the message, use {@link #PERSON_USER_SELF} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use - * {@link #PERSON_USER_REMOTE} to represent a remote user. + * {@link #PERSON_USER_OTHERS} to represent a remote user. */ public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); diff --git a/core/java/android/view/textclassifier/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 7679c5b63274..cc22ff02e338 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -28,6 +28,8 @@ #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" +#include "SkFont.h" +#include "SkFontMetrics.h" #include "SkFontTypes.h" #include "SkMaskFilter.h" #include "SkPath.h" @@ -69,9 +71,21 @@ static JMetricsID gFontMetrics_fieldID; static jclass gFontMetricsInt_class; static JMetricsID gFontMetricsInt_fieldID; -static void defaultSettingsForAndroid(Paint* paint) { - // GlyphID encoding is required because we are using Harfbuzz shaping - paint->setTextEncoding(kGlyphID_SkTextEncoding); +static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, + const SkPoint pos[], SkPath* dst) { + struct Rec { + SkPath* fDst; + const SkPoint* fPos; + } rec = { dst, pos }; + font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) { + Rec* rec = (Rec*)ctx; + if (src) { + SkMatrix tmp(mx); + tmp.postTranslate(rec->fPos->fX, rec->fPos->fY); + rec->fDst->addPath(*src, tmp); + } + rec->fPos += 1; + }, &rec); } namespace PaintGlue { @@ -88,18 +102,7 @@ namespace PaintGlue { } static jlong init(JNIEnv* env, jobject) { - static_assert(1 << 0 == SkPaint::kAntiAlias_Flag, "paint_flags_mismatch"); - static_assert(1 << 2 == SkPaint::kDither_Flag, "paint_flags_mismatch"); - static_assert(1 << 3 == SkPaint::kUnderlineText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 5 == SkPaint::kFakeBoldText_Flag, "paint_flags_mismatch"); - static_assert(1 << 6 == SkPaint::kLinearText_Flag, "paint_flags_mismatch"); - static_assert(1 << 7 == SkPaint::kSubpixelText_Flag, "paint_flags_mismatch"); - static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag, "paint_flags_mismatch"); - - Paint* obj = new Paint(); - defaultSettingsForAndroid(obj); - return reinterpret_cast<jlong>(obj); + return reinterpret_cast<jlong>(new Paint); } static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) { @@ -288,10 +291,11 @@ namespace PaintGlue { pos[i].fX = x + layout.getX(i); pos[i].fY = y + layout.getY(i); } + const SkFont& font = paint->getSkFont(); if (start == 0) { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path); + getPosTextPath(font, glyphs, end, pos, path); } else { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath); + getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath); path->addPath(tmpPath); } } @@ -321,7 +325,6 @@ namespace PaintGlue { x += MinikinUtils::xOffsetForTextAlign(paint, layout); Paint::Align align = paint->getTextAlign(); paint->setTextAlign(Paint::kLeft_Align); - paint->setTextEncoding(kGlyphID_SkTextEncoding); GetTextFunctor f(layout, path, x, y, paint, glyphs, pos); MinikinUtils::forFontRun(layout, paint, f); paint->setTextAlign(align); @@ -584,20 +587,21 @@ namespace PaintGlue { const int kElegantDescent = -500; const int kElegantLeading = 0; Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SkFont* font = &paint->getSkFont(); const Typeface* typeface = paint->getAndroidTypeface(); typeface = Typeface::resolveDefault(typeface); minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle); - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); - MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery); - SkScalar spacing = paint->getFontMetrics(metrics); + float saveSkewX = font->getSkewX(); + bool savefakeBold = font->isEmbolden(); + MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery); + SkScalar spacing = font->getMetrics(metrics); // The populateSkPaint call may have changed fake bold / text skew // because we want to measure with those effects applied, so now // restore the original settings. - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + font->setSkewX(saveSkewX); + font->setEmbolden(savefakeBold); if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) { - SkScalar size = paint->getTextSize(); + SkScalar size = font->getSize(); metrics->fTop = -size * kElegantTop / 2048; metrics->fBottom = -size * kElegantBottom / 2048; metrics->fAscent = -size * kElegantAscent / 2048; @@ -646,9 +650,7 @@ namespace PaintGlue { // ------------------ @CriticalNative --------------------------- static void reset(jlong objHandle) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - obj->reset(); - defaultSettingsForAndroid(obj); + reinterpret_cast<Paint*>(objHandle)->reset(); } static void assign(jlong dstPaintHandle, jlong srcPaintHandle) { @@ -657,40 +659,22 @@ namespace PaintGlue { *dst = *src; } - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - static jint getFlags(jlong paintHandle) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - uint32_t result = nativePaint->getFlags(); - result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away. - if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) { - result |= sFilterBitmapFlag; - } - return static_cast<jint>(result); + uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags(); + return static_cast<jint>(flags); } static void setFlags(jlong paintHandle, jint flags) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - // Instead of modifying 0x02, change the filter level. - nativePaint->setFilterQuality(flags & sFilterBitmapFlag - ? kLow_SkFilterQuality - : kNone_SkFilterQuality); - // Don't pass through filter flag, which is no longer stored in paint's flags. - flags &= ~sFilterBitmapFlag; - // Use the existing value for 0x02. - const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag; - flags |= existing0x02Flag; - nativePaint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags); } static jint getHinting(jlong paintHandle) { - 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); } @@ -699,37 +683,23 @@ namespace PaintGlue { } static void setLinearText(jlong paintHandle, jboolean linearText) { - reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText); } static void setSubpixelText(jlong paintHandle, jboolean subpixelText) { - reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText); } static void setUnderlineText(jlong paintHandle, jboolean underlineText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (underlineText) { - flags |= Paint::kUnderlineText_ReserveFlag; - } else { - flags &= ~Paint::kUnderlineText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText); } static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (strikeThruText) { - flags |= Paint::kStrikeThruText_ReserveFlag; - } else { - flags &= ~Paint::kStrikeThruText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText); } static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) { - reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText); } static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) { @@ -907,27 +877,29 @@ namespace PaintGlue { } static jfloat getTextSize(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize()); } static void setTextSize(jlong paintHandle, jfloat textSize) { - reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize); + if (textSize >= 0) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize); + } } static jfloat getTextScaleX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX()); } static void setTextScaleX(jlong paintHandle, jfloat scaleX) { - reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX); } static jfloat getTextSkewX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX()); } static void setTextSkewX(jlong paintHandle, jfloat skewX) { - reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX); } static jfloat getLetterSpacing(jlong paintHandle) { @@ -979,7 +951,7 @@ namespace PaintGlue { if (metrics.hasUnderlinePosition(&position)) { return SkScalarToFloat(position); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Top * textSize); } } @@ -991,18 +963,18 @@ namespace PaintGlue { if (metrics.hasUnderlineThickness(&thickness)) { return SkScalarToFloat(thickness); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize); } } static jfloat getStrikeThruPosition(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); } static jfloat getStrikeThruThickness(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp index 182b22b3c917..4fe9140572d3 100644 --- a/core/jni/android/graphics/PaintFilter.cpp +++ b/core/jni/android/graphics/PaintFilter.cpp @@ -21,6 +21,7 @@ #include "core_jni_helpers.h" +#include "hwui/Paint.h" #include "hwui/PaintFilter.h" #include "SkPaint.h" @@ -29,11 +30,15 @@ namespace android { class PaintFlagsFilter : public PaintFilter { public: PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) { - fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags); - fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags); + fClearFlags = static_cast<uint16_t>(clearFlags); + fSetFlags = static_cast<uint16_t>(setFlags); } void filter(SkPaint* paint) override { - paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags); + uint32_t flags = Paint::GetSkPaintJavaFlags(*paint); + Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags); + } + void filterFullPaint(Paint* paint) override { + paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags); } private: @@ -41,33 +46,6 @@ private: uint16_t fSetFlags; }; -// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality. -class CompatPaintFlagsFilter : public PaintFlagsFilter { -public: - CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality) - : PaintFlagsFilter(clearFlags, setFlags) - , fDesiredQuality(desiredQuality) { - } - - virtual void filter(SkPaint* paint) { - PaintFlagsFilter::filter(paint); - paint->setFilterQuality(fDesiredQuality); - } - -private: - const SkFilterQuality fDesiredQuality; -}; - -// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it. -static inline bool hadFiltering(jint& flags) { - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - - const bool result = (flags & sFilterBitmapFlag) != 0; - flags &= ~sFilterBitmapFlag; - return result; -} - class PaintFilterGlue { public: @@ -78,29 +56,11 @@ public: static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz, jint clearFlags, jint setFlags) { + PaintFilter* filter = nullptr; if (clearFlags | setFlags) { - // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no - // longer has a Skia equivalent flag (instead it corresponds to - // calling setFilterQuality), and keep track of which group(s), if - // any, had the flag set. - const bool turnFilteringOn = hadFiltering(setFlags); - const bool turnFilteringOff = hadFiltering(clearFlags); - - PaintFilter* filter; - if (turnFilteringOn) { - // Turning filtering on overrides turning it off. - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kLow_SkFilterQuality); - } else if (turnFilteringOff) { - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kNone_SkFilterQuality); - } else { - filter = new PaintFlagsFilter(clearFlags, setFlags); - } - return reinterpret_cast<jlong>(filter); - } else { - return NULL; + filter = new PaintFlagsFilter(clearFlags, setFlags); } + return reinterpret_cast<jlong>(filter); } }; diff --git a/core/jni/android_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.bp b/core/proto/Android.bp index 80cc2d4cab94..3b891d6b4947 100644 --- a/core/proto/Android.bp +++ b/core/proto/Android.bp @@ -21,7 +21,10 @@ cc_library_static { type: "lite", }, srcs: [ + "android/bluetooth/a2dp/enums.proto", "android/bluetooth/enums.proto", "android/bluetooth/hci/enums.proto", + "android/bluetooth/hfp/enums.proto", + "android/bluetooth/smp/enums.proto", ], } 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/a2dp/enums.proto b/core/proto/android/bluetooth/a2dp/enums.proto new file mode 100644 index 000000000000..5a025bdd6c10 --- /dev/null +++ b/core/proto/android/bluetooth/a2dp/enums.proto @@ -0,0 +1,35 @@ +/* + * 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. + */ + +syntax = "proto2"; +package android.bluetooth.a2dp; + +option java_outer_classname = "BluetoothA2dpProtoEnums"; +option java_multiple_files = true; + +// A2dp playback state enum, defined from: +// frameworks/base/core/java/android/bluetooth/BluetoothA2dp.java +enum PlaybackStateEnum { + PLAYBACK_STATE_UNKNOWN = 0; + PLAYBACK_STATE_PLAYING = 10; + PLAYBACK_STATE_NOT_PLAYING = 11; +} + +enum AudioCodingModeEnum { + AUDIO_CODING_MODE_UNKNOWN = 0; + AUDIO_CODING_MODE_HARDWARE = 1; + AUDIO_CODING_MODE_SOFTWARE = 2; +} diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto index 76c240ecff4d..a88a06cf091c 100644 --- a/core/proto/android/bluetooth/enums.proto +++ b/core/proto/android/bluetooth/enums.proto @@ -56,3 +56,57 @@ enum LinkTypeEnum { LINK_TYPE_ACL = 0x01; LINK_TYPE_ESCO = 0x02; } + +enum DeviceInfoSrcEnum { + DEVICE_INFO_SRC_UNKNOWN = 0; + // Within Android Bluetooth stack + DEVICE_INFO_INTERNAL = 1; + // Outside Android Bluetooth stack + DEVICE_INFO_EXTERNAL = 2; +} + +enum DeviceTypeEnum { + DEVICE_TYPE_UNKNOWN = 0; + DEVICE_TYPE_CLASSIC = 1; + DEVICE_TYPE_LE = 2; + DEVICE_TYPE_DUAL = 3; +} + +// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java +enum TransportTypeEnum { + TRANSPORT_TYPE_AUTO = 0; + TRANSPORT_TYPE_BREDR = 1; + TRANSPORT_TYPE_LE = 2; +} + +// Bond state enum +// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java +enum BondStateEnum { + BOND_STATE_UNKNOWN = 0; + BOND_STATE_NONE = 10; + BOND_STATE_BONDING = 11; + BOND_STATE_BONDED = 12; +} + +// Sub states within the bonding general state +enum BondSubStateEnum { + BOND_SUB_STATE_UNKNOWN = 0; + BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED = 1; + BOND_SUB_STATE_LOCAL_PIN_REQUESTED = 2; + BOND_SUB_STATE_LOCAL_PIN_REPLIED = 3; + BOND_SUB_STATE_LOCAL_SSP_REQUESTED = 4; + BOND_SUB_STATE_LOCAL_SSP_REPLIED = 5; +} + +enum UnbondReasonEnum { + UNBOND_REASON_UNKNOWN = 0; + UNBOND_REASON_AUTH_FAILED = 1; + UNBOND_REASON_AUTH_REJECTED = 2; + UNBOND_REASON_AUTH_CANCELED = 3; + UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; + UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; + UNBOND_REASON_AUTH_TIMEOUT = 6; + UNBOND_REASON_REPEATED_ATTEMPTS = 7; + UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; + UNBOND_REASON_REMOVED = 9; +} diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto index e1d96bbce68a..ef894e548351 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; @@ -517,3 +517,43 @@ enum StatusEnum { STATUS_CLB_DATA_TOO_BIG = 0x43; STATUS_OPERATION_CANCELED_BY_HOST = 0x44; // Not currently used in system/bt } + +enum BqrIdEnum { + BQR_ID_UNKNOWN = 0x00; + BQR_ID_MONITOR_MODE = 0x01; + BQR_ID_APPROACH_LSTO = 0x02; + BQR_ID_A2DP_AUDIO_CHOPPY = 0x03; + BQR_ID_SCO_VOICE_CHOPPY = 0x04; +} + +enum BqrPacketTypeEnum { + BQR_PACKET_TYPE_UNKNOWN = 0x00; + BQR_PACKET_TYPE_ID = 0x01; + BQR_PACKET_TYPE_NULL = 0x02; + BQR_PACKET_TYPE_POLL = 0x03; + BQR_PACKET_TYPE_FHS = 0x04; + BQR_PACKET_TYPE_HV1 = 0x05; + BQR_PACKET_TYPE_HV2 = 0x06; + BQR_PACKET_TYPE_HV3 = 0x07; + BQR_PACKET_TYPE_DV = 0x08; + BQR_PACKET_TYPE_EV3 = 0x09; + BQR_PACKET_TYPE_EV4 = 0x0A; + BQR_PACKET_TYPE_EV5 = 0x0B; + BQR_PACKET_TYPE_2EV3 = 0x0C; + BQR_PACKET_TYPE_2EV5 = 0x0D; + BQR_PACKET_TYPE_3EV3 = 0x0E; + BQR_PACKET_TYPE_3EV5 = 0x0F; + BQR_PACKET_TYPE_DM1 = 0x10; + BQR_PACKET_TYPE_DH1 = 0x11; + BQR_PACKET_TYPE_DM3 = 0x12; + BQR_PACKET_TYPE_DH3 = 0x13; + BQR_PACKET_TYPE_DM5 = 0x14; + BQR_PACKET_TYPE_DH5 = 0x15; + BQR_PACKET_TYPE_AUX1 = 0x16; + BQR_PACKET_TYPE_2DH1 = 0x17; + BQR_PACKET_TYPE_2DH3 = 0x18; + BQR_PACKET_TYPE_2DH5 = 0x19; + BQR_PACKET_TYPE_3DH1 = 0x1A; + BQR_PACKET_TYPE_3DH3 = 0x1B; + BQR_PACKET_TYPE_3DH5 = 0x1C; +} diff --git a/core/proto/android/bluetooth/smp/enums.proto b/core/proto/android/bluetooth/smp/enums.proto new file mode 100644 index 000000000000..c6747b78dc29 --- /dev/null +++ b/core/proto/android/bluetooth/smp/enums.proto @@ -0,0 +1,58 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.bluetooth.smp; + +option java_outer_classname = "BluetoothSmpProtoEnums"; +option java_multiple_files = true; + +// SMP Pairing command codes +enum CommandEnum { + CMD_UNKNOWN = 0x00; + CMD_PAIRING_REQUEST = 0x01; + CMD_PAIRING_RESPONSE = 0x02; + CMD_PAIRING_CONFIRM = 0x03; + CMD_PAIRING_RANDOM = 0x04; + CMD_PAIRING_FAILED = 0x05; + CMD_ENCRYPTION_INFON = 0x06; + CMD_MASTER_IDENTIFICATION = 0x07; + CMD_IDENTITY_INFO = 0x08; + CMD_IDENTITY_ADDR_INFO = 0x09; + CMD_SIGNING_INFO = 0x0A; + CMD_SECURITY_REQUEST = 0x0B; + CMD_PAIRING_PUBLIC_KEY = 0x0C; + CMD_PAIRING_DHKEY_CHECK = 0x0D; + CMD_PAIRING_KEYPRESS_INFO = 0x0E; +} + +enum PairingFailReasonEnum { + PAIRING_FAIL_REASON_RESERVED = 0x00; + PAIRING_FAIL_REASON_PASSKEY_ENTRY = 0x01; + PAIRING_FAIL_REASON_OOB = 0x02; + PAIRING_FAIL_REASON_AUTH_REQ = 0x03; + PAIRING_FAIL_REASON_CONFIRM_VALUE = 0x04; + PAIRING_FAIL_REASON_PAIR_NOT_SUPPORT = 0x05; + PAIRING_FAIL_REASON_ENC_KEY_SIZE = 0x06; + PAIRING_FAIL_REASON_INVALID_CMD = 0x07; + PAIRING_FAIL_REASON_UNSPECIFIED = 0x08; + PAIRING_FAIL_REASON_REPEATED_ATTEMPTS = 0x09; + PAIRING_FAIL_REASON_INVALID_PARAMETERS = 0x0A; + PAIRING_FAIL_REASON_DHKEY_CHK = 0x0B; + PAIRING_FAIL_REASON_NUMERIC_COMPARISON = 0x0C; + PAIRING_FAIL_REASON_CLASSIC_PAIRING_IN_PROGR = 0x0D; + PAIRING_FAIL_REASON_XTRANS_DERIVE_NOT_ALLOW = 0x0E; +}
\ No newline at end of file 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/packages/SettingsLib/res/drawable/btn_borderless_rect.xml b/core/res/res/drawable/btn_borderless_rect.xml index 9eaba8364f8e..9eaba8364f8e 100644 --- a/packages/SettingsLib/res/drawable/btn_borderless_rect.xml +++ b/core/res/res/drawable/btn_borderless_rect.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml b/core/res/res/drawable/ic_bt_cellphone.xml index be9b094134c8..be9b094134c8 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml +++ b/core/res/res/drawable/ic_bt_cellphone.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml b/core/res/res/drawable/ic_bt_headphones_a2dp.xml index 32f39a39754f..32f39a39754f 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml +++ b/core/res/res/drawable/ic_bt_headphones_a2dp.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml b/core/res/res/drawable/ic_bt_headset_hfp.xml index e43fe39409af..e43fe39409af 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml +++ b/core/res/res/drawable/ic_bt_headset_hfp.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_hearing_aid.xml b/core/res/res/drawable/ic_bt_hearing_aid.xml index e14c99b61e2f..e14c99b61e2f 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_hearing_aid.xml +++ b/core/res/res/drawable/ic_bt_hearing_aid.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_laptop.xml b/core/res/res/drawable/ic_bt_laptop.xml index 029e4d913434..029e4d913434 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_laptop.xml +++ b/core/res/res/drawable/ic_bt_laptop.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml b/core/res/res/drawable/ic_bt_misc_hid.xml index ac460ab675e9..ac460ab675e9 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml +++ b/core/res/res/drawable/ic_bt_misc_hid.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml b/core/res/res/drawable/ic_bt_network_pan.xml index 6e4361b95eab..6e4361b95eab 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml +++ b/core/res/res/drawable/ic_bt_network_pan.xml diff --git a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml b/core/res/res/drawable/ic_bt_pointing_hid.xml index de97e249789f..de97e249789f 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml +++ b/core/res/res/drawable/ic_bt_pointing_hid.xml diff --git a/packages/SettingsLib/res/drawable/ic_expand_more.xml b/core/res/res/drawable/ic_expand_more.xml index a8ff5397c839..a8ff5397c839 100644 --- a/packages/SettingsLib/res/drawable/ic_expand_more.xml +++ b/core/res/res/drawable/ic_expand_more.xml diff --git a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml b/core/res/res/drawable/ic_lockscreen_ime.xml index 4b81a3c9c460..4b81a3c9c460 100644 --- a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml +++ b/core/res/res/drawable/ic_lockscreen_ime.xml diff --git a/packages/SettingsLib/res/drawable/ic_menu.xml b/core/res/res/drawable/ic_menu.xml index b77db08b336d..b77db08b336d 100644 --- a/packages/SettingsLib/res/drawable/ic_menu.xml +++ b/core/res/res/drawable/ic_menu.xml diff --git a/packages/SettingsLib/res/drawable/ic_minus.xml b/core/res/res/drawable/ic_minus.xml index 9a929a44c665..9a929a44c665 100644 --- a/packages/SettingsLib/res/drawable/ic_minus.xml +++ b/core/res/res/drawable/ic_minus.xml diff --git a/packages/SettingsLib/res/drawable/ic_mode_edit.xml b/core/res/res/drawable/ic_mode_edit.xml index 319e7eb230d5..319e7eb230d5 100644 --- a/packages/SettingsLib/res/drawable/ic_mode_edit.xml +++ b/core/res/res/drawable/ic_mode_edit.xml diff --git a/packages/SettingsLib/res/drawable/ic_plus.xml b/core/res/res/drawable/ic_plus.xml index 2a10e707df2a..2a10e707df2a 100644 --- a/packages/SettingsLib/res/drawable/ic_plus.xml +++ b/core/res/res/drawable/ic_plus.xml diff --git a/packages/SettingsLib/res/drawable/ic_qs_night_display_on.xml b/core/res/res/drawable/ic_qs_night_display_on.xml index 35907cc83fe0..35907cc83fe0 100644 --- a/packages/SettingsLib/res/drawable/ic_qs_night_display_on.xml +++ b/core/res/res/drawable/ic_qs_night_display_on.xml diff --git a/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml b/core/res/res/drawable/ic_settings_bluetooth.xml index 6e32e1a7f631..6e32e1a7f631 100644 --- a/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml +++ b/core/res/res/drawable/ic_settings_bluetooth.xml diff --git a/packages/SettingsLib/res/drawable/ic_settings_print.xml b/core/res/res/drawable/ic_settings_print.xml index 68b627cf4dc9..68b627cf4dc9 100644 --- a/packages/SettingsLib/res/drawable/ic_settings_print.xml +++ b/core/res/res/drawable/ic_settings_print.xml diff --git a/packages/SettingsLib/res/drawable/ic_signal_location.xml b/core/res/res/drawable/ic_signal_location.xml index 11870933b8bb..11870933b8bb 100644 --- a/packages/SettingsLib/res/drawable/ic_signal_location.xml +++ b/core/res/res/drawable/ic_signal_location.xml 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 d5561302fdc6..47bd9da65870 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1423,6 +1423,27 @@ <java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" /> <java-symbol type="drawable" name="ic_signal_cellular_alt_24px" /> + <java-symbol type="drawable" name="btn_borderless_rect" /> + <java-symbol type="drawable" name="ic_bt_cellphone" /> + <java-symbol type="drawable" name="ic_bt_headphones_a2dp" /> + <java-symbol type="drawable" name="ic_bt_headset_hfp" /> + <java-symbol type="drawable" name="ic_bt_hearing_aid" /> + <java-symbol type="drawable" name="ic_bt_laptop" /> + <java-symbol type="drawable" name="ic_bt_misc_hid" /> + <java-symbol type="drawable" name="ic_bt_network_pan" /> + <java-symbol type="drawable" name="ic_bt_pointing_hid" /> + <java-symbol type="drawable" name="ic_expand_more" /> + <java-symbol type="drawable" name="ic_lockscreen_ime" /> + <java-symbol type="drawable" name="ic_menu" /> + <java-symbol type="drawable" name="ic_minus" /> + <java-symbol type="drawable" name="ic_mode_edit" /> + <java-symbol type="drawable" name="ic_plus" /> + <java-symbol type="drawable" name="ic_qs_night_display_on" /> + <java-symbol type="drawable" name="ic_settings_bluetooth" /> + <java-symbol type="drawable" name="ic_settings_print" /> + <java-symbol type="drawable" name="ic_signal_location" /> + <java-symbol type="drawable" name="ic_info_outline_24" /> + <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" /> <java-symbol type="drawable" name="autofilled_highlight"/> <java-symbol type="drawable" name="ic_camera" /> @@ -3554,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 0fc3bd224fbf..9d04e6392fd0 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -49,10 +49,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ telephony-common \ + testables \ 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/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index d44745121a22..8f2109676dfb 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -16,15 +16,25 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; - +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets.Type; +import android.view.WindowManager.BadTokenException; +import android.view.WindowManager.LayoutParams; +import android.widget.TextView; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -37,8 +47,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class InsetsControllerTest { - private InsetsController mController = new InsetsController(mock(ViewRootImpl.class)); - + private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; @@ -47,6 +56,24 @@ public class InsetsControllerTest { mLeash = new SurfaceControl.Builder(mSession) .setName("testSurface") .build(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Context context = InstrumentationRegistry.getTargetContext(); + // cannot mock ViewRootImpl since it's final. + ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + try { + viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + } catch (BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mController = new InsetsController(viewRootImpl); + final Rect rect = new Rect(5, 5, 5, 5); + mController.calculateInsets( + false, + false, + new DisplayCutout( + Insets.of(10, 10, 10, 10), rect, rect, rect, rect), + rect, rect); + }); } @Test @@ -64,4 +91,39 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[0]); assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl()); } + + @Test + public void testAnimationEndState() { + final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash); + final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash); + final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash); + + InsetsSourceControl[] controls = new InsetsSourceControl[3]; + controls[0] = navBar; + controls[1] = topBar; + controls[2] = ime; + mController.onControlsChanged(controls); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mController.show(Type.all()); + // quickly jump to final state by cancelling it. + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.all()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.show(Type.ime()); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.ime()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + }); + } } diff --git a/core/tests/coretests/src/android/view/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/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index 780e15ab885e..5022e305ecc2 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,8 +16,8 @@ package android.view.textclassifier; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; import static com.google.common.truth.Truth.assertThat; @@ -58,7 +58,7 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); + new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( @@ -81,7 +81,7 @@ public class ActionsSuggestionsHelperTest { .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_LOCAL) + new ConversationActions.Message.Builder(PERSON_USER_SELF) .setText("third") .build(); ConversationActions.Message fourthMessage = @@ -104,16 +104,16 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("first") .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("third") .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); diff --git a/core/tests/coretests/src/android/view/textclassifier/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/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 4d78e4036e74..5e58f82038f1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -378,7 +378,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = @@ -407,7 +407,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = diff --git a/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/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 25f6775f81b4..cb1f69c0ada3 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -33,6 +33,7 @@ import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; @@ -46,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; +import dalvik.system.VMRuntime; import libcore.util.NativeAllocationRegistry; @@ -243,7 +245,16 @@ public class Typeface { if (familyBuilder == null) { familyBuilder = new FontFamily.Builder(fontBuilder.build()); } else { - familyBuilder.addFont(fontBuilder.build()); + try { + familyBuilder.addFont(fontBuilder.build()); + } catch (IllegalArgumentException e) { + if (VMRuntime.getRuntime().getTargetSdkVersion() <= VERSION_CODES.P) { + // Surpress the IllegalArgumentException for keeping the backward + // compatibility. + continue; + } + throw e; + } } } if (familyBuilder == null) { 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/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3c35d9b33fc8..20303eba6667 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -562,7 +562,7 @@ std::string AssetManager2::GetLastResourceResolution() const { if (package != nullptr) { ToResourceName(last_resolution.type_string_ref, last_resolution.entry_string_ref, - package, + package->GetPackageName(), &resource_name); resource_name_string = ToFormattedResourceString(&resource_name); } @@ -607,15 +607,25 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons return false; } - const LoadedPackage* package = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); - if (package == nullptr) { + const uint8_t package_idx = package_ids_[get_package_id(resid)]; + if (package_idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + get_package_id(resid), resid); + return false; + } + + const PackageGroup& package_group = package_groups_[package_idx]; + auto cookie_iter = std::find(package_group.cookies_.begin(), + package_group.cookies_.end(), cookie); + if (cookie_iter == package_group.cookies_.end()) { return false; } + long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter); + const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_; return ToResourceName(entry.type_string_ref, entry.entry_string_ref, - package, + package->GetPackageName(), out_name); } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 645984d85c34..c63dff8f9104 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -48,12 +48,12 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin !(has_type_separator && out_type->empty()); } -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name) { - out_name->package = package->GetPackageName().data(); - out_name->package_len = package->GetPackageName().size(); + out_name->package = package_name.data(); + out_name->package_len = package_name.size(); out_name->type = type_string_ref.string8(&out_name->type_len); out_name->type16 = nullptr; diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index eb6eb8e66175..e649940cdde1 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,12 +28,11 @@ namespace android { bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); -// Convert a type_string_ref, entry_string_ref, and package -// to AssetManager2::ResourceName. Useful for getting -// resource name without re-running AssetManager2::FindEntry searches. -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. +// Useful for getting resource name without re-running AssetManager2::FindEntry searches. +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name); // Formats a ResourceName to "package:type/entry_name". diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 105dcd209bf7..447fdf5d306a 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -210,6 +210,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({lib_one_assets_.get()}); + + AssetManager2::ResourceName name; + ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name)); + std::string formatted_name = ToFormattedResourceString(&name); + ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo"); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index cc62fdc76ef8..54a91f4ec06f 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -682,12 +682,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { if (count <= 0 || paint.nothingToDraw()) return; - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and // older. if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 && @@ -710,12 +709,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) { - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); const int N = end - start; SkTextBlobBuilder builder; diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 277148e3d556..523148672033 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -39,34 +39,28 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca } void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) { - uint32_t flags; - PaintFilter* paintFilter = getPaintFilter(); - if (paintFilter) { - SkPaint paintCopy(paint); - paintFilter->filter(&paintCopy); - flags = paintCopy.getFlags(); - } else { - flags = paint.getFlags(); - } - if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { + // paint has already been filtered by our caller, so we can ignore any filter + const bool strikeThru = paint.isStrikeThru(); + const bool underline = paint.isUnderline(); + if (strikeThru || underline) { const SkScalar left = x; const SkScalar right = x + length; - if (flags & SkPaint::kUnderlineText_ReserveFlag) { + const float textSize = paint.getSkFont().getSize(); + if (underline) { SkFontMetrics metrics; - paint.getFontMetrics(&metrics); + paint.getSkFont().getMetrics(&metrics); SkScalar position; if (!metrics.hasUnderlinePosition(&position)) { - position = paint.getTextSize() * Paint::kStdUnderline_Top; + position = textSize * Paint::kStdUnderline_Top; } SkScalar thickness; if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + thickness = textSize * Paint::kStdUnderline_Thickness; } const SkScalar top = y + position; drawStroke(left, right, top, thickness, paint, this); } - if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - const float textSize = paint.getTextSize(); + if (strikeThru) { const float position = textSize * Paint::kStdStrikeThru_Top; const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position; @@ -75,19 +69,19 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa } } -static void simplifyPaint(int color, SkPaint* paint) { +static void simplifyPaint(int color, Paint* paint) { paint->setColor(color); paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); + paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); } class DrawTextFunctor { public: - DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x, + DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, float y, minikin::MinikinRect& bounds, float totalAdvance) : layout(layout) , canvas(canvas) @@ -123,14 +117,14 @@ public: bool darken = channelSum < (128 * 3); // outline - SkPaint outlinePaint(paint); + Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); // inner - SkPaint innerPaint(paint); + Paint innerPaint(paint); simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); innerPaint.setStyle(SkPaint::kFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop, @@ -145,7 +139,7 @@ public: private: const minikin::Layout& layout; Canvas* canvas; - const SkPaint& paint; + const Paint& paint; float x; float y; minikin::MinikinRect& bounds; diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 84292c8768c1..375f5bc9df37 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -17,8 +17,9 @@ #include "MinikinSkia.h" #include <SkFontDescriptor.h> +#include <SkFont.h> +#include <SkFontMetrics.h> #include <SkFontMgr.h> -#include <SkPaint.h> #include <SkTypeface.h> #include <log/log.h> @@ -40,25 +41,24 @@ MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontDat , mAxes(axes) , mFilePath(filePath) {} -static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint, - const minikin::MinikinPaint& paint, - const minikin::FontFakery& fakery) { - skPaint->setTextEncoding(kGlyphID_SkTextEncoding); - skPaint->setTextSize(paint.size); - skPaint->setTextScaleX(paint.scaleX); - skPaint->setTextSkewX(paint.skewX); - MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); +static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery) { + skFont->setSize(paint.size); + skFont->setScaleX(paint.scaleX); + skFont->setSkewX(paint.skewX); + MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags); // Apply font fakery on top of user-supplied flags. - MinikinFontSkia::populateSkPaint(skPaint, font, fakery); + MinikinFontSkia::populateSkFont(skFont, font, fakery); } float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkScalar skWidth; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, &skWidth); #ifdef VERBOSE ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth); #endif @@ -68,11 +68,11 @@ float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::Mi void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkRect skBounds; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, nullptr, &skBounds); bounds->mLeft = skBounds.fLeft; bounds->mTop = skBounds.fTop; bounds->mRight = skBounds.fRight; @@ -82,10 +82,10 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); + SkFont skFont; + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); SkFontMetrics metrics; - skPaint.getFontMetrics(&metrics); + skFont.getMetrics(&metrics); extent->ascent = metrics.fAscent; extent->descent = metrics.fDescent; } @@ -137,28 +137,36 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( ttcIndex, variations); } -uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { - uint32_t flags = paint->getFlags(); - unsigned hinting = static_cast<unsigned>(paint->getHinting()); - // select only flags that might affect text layout - flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | - SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag | - SkPaint::kAutoHinting_Flag); - flags |= (hinting << 16); +// hinting<<16 | edging<<8 | bools:5bits +uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) { + uint32_t flags = (unsigned)font.getHinting() << 16; + flags |= (unsigned)font.getEdging() << 8; + flags |= font.isEmbolden() << minikin::Embolden_Shift; + flags |= font.isLinearMetrics() << minikin::LinearMetrics_Shift; + flags |= font.isSubpixel() << minikin::Subpixel_Shift; + flags |= font.isEmbeddedBitmaps() << minikin::EmbeddedBitmaps_Shift; + flags |= font.isForceAutoHinting() << minikin::ForceAutoHinting_Shift; return flags; } -void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { - paint->setFlags(paintFlags & SkPaint::kAllFlags); - paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16)); +void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) { + // We store hinting in the top 16 bits (only need 2 of them) + font->setHinting((SkFontHinting)(flags >> 16)); + // We store edging in bits 8:15 (only need 2 of them) + font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF)); + font->setEmbolden( (flags & minikin::Embolden_Flag) != 0); + font->setLinearMetrics( (flags & minikin::LinearMetrics_Flag) != 0); + font->setSubpixel( (flags & minikin::Subpixel_Flag) != 0); + font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0); + font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0); } -void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, - minikin::FontFakery fakery) { - paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); - paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold()); +void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font, + minikin::FontFakery fakery) { + skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); + skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold()); if (fakery.isFakeItalic()) { - paint->setTextSkewX(paint->getTextSkewX() - 0.25f); + skFont->setSkewX(skFont->getSkewX() - 0.25f); } } } diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index 55576b7bfa4e..ad46b2391cac 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -21,7 +21,7 @@ #include <cutils/compiler.h> #include <minikin/MinikinFont.h> -class SkPaint; +class SkFont; class SkTypeface; namespace android { @@ -54,12 +54,12 @@ public: std::shared_ptr<minikin::MinikinFont> createFontWithVariation( const std::vector<minikin::FontVariation>&) const; - static uint32_t packPaintFlags(const SkPaint* paint); - static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); + static uint32_t packFontFlags(const SkFont&); + static void unpackFontFlags(SkFont*, uint32_t fontFlags); // set typeface and fake bold/italic parameters - static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font, - minikin::FontFakery fakery); + static void populateSkFont(SkFont*, const minikin::MinikinFont* font, + minikin::FontFakery fakery); private: sk_sp<SkTypeface> mTypeface; diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index ba240feb4f41..733f8e415270 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -30,16 +30,17 @@ namespace android { minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, const Typeface* typeface) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + const SkFont& font = paint->getSkFont(); minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection); /* Prepare minikin Paint */ minikinPaint.size = - paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize()); - minikinPaint.scaleX = paint->getTextScaleX(); - minikinPaint.skewX = paint->getTextSkewX(); + font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize()); + minikinPaint.scaleX = font.getScaleX(); + minikinPaint.skewX = font.getSkewX(); minikinPaint.letterSpacing = paint->getLetterSpacing(); minikinPaint.wordSpacing = paint->getWordSpacing(); - minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint); + minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font); minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index d27d54454ea0..cbf409504675 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -63,27 +63,29 @@ public: // f is a functor of type void f(size_t start, size_t end); template <typename F> ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) { - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); + float saveSkewX = paint->getSkFont().getSkewX(); + bool savefakeBold = paint->getSkFont().isEmbolden(); const minikin::MinikinFont* curFont = nullptr; size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { const minikin::MinikinFont* nextFont = layout.getFont(i); if (i > 0 && nextFont != curFont) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, i); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); start = i; } curFont = nextFont; } if (nGlyphs > start) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, nGlyphs); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); } } }; diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 92ffda9486bb..601b3c23cc1f 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -21,6 +21,7 @@ #include <cutils/compiler.h> +#include <SkFont.h> #include <SkPaint.h> #include <string> @@ -46,7 +47,6 @@ public: Paint(); Paint(const Paint& paint); - Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor) ~Paint(); Paint& operator=(const Paint& other); @@ -54,6 +54,17 @@ public: friend bool operator==(const Paint& a, const Paint& b); friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); } + SkFont& getSkFont() { return mFont; } + const SkFont& getSkFont() const { return mFont; } + + // These shadow the methods on SkPaint, but we need to so we can keep related + // attributes in-sync. + + void reset(); + void setAntiAlias(bool); + + // End method shadowing + void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; } float getLetterSpacing() const { return mLetterSpacing; } @@ -94,7 +105,31 @@ public: Align getTextAlign() const { return mAlign; } void setTextAlign(Align align) { mAlign = align; } + bool isStrikeThru() const { return mStrikeThru; } + void setStrikeThru(bool st) { mStrikeThru = st; } + + bool isUnderline() const { return mUnderline; } + void setUnderline(bool u) { mUnderline = u; } + + bool isDevKern() const { return mDevKern; } + void setDevKern(bool d) { mDevKern = d; } + + // The Java flags (Paint.java) no longer fit into the native apis directly. + // These methods handle converting to and from them and the native representations + // in android::Paint. + + uint32_t getJavaFlags() const; + void setJavaFlags(uint32_t); + + // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags + // that are meant for SkFont or Paint (e.g. underline, strikethru) + // The only respected flags are : [ antialias, dither, filterBitmap ] + static uint32_t GetSkPaintJavaFlags(const SkPaint&); + static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags); + private: + SkFont mFont; + float mLetterSpacing = 0; float mWordSpacing = 0; std::string mFontFeatureSettings; @@ -107,6 +142,9 @@ private: // nullptr is valid: it means the default typeface. const Typeface* mTypeface = nullptr; Align mAlign = kLeft_Align; + bool mStrikeThru = false; + bool mUnderline = false; + bool mDevKern = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h index bf5627eac229..0e7b61977000 100644 --- a/libs/hwui/hwui/PaintFilter.h +++ b/libs/hwui/hwui/PaintFilter.h @@ -12,6 +12,7 @@ public: * The implementation may modify the paint as they wish. */ virtual void filter(SkPaint*) = 0; + virtual void filterFullPaint(Paint*) = 0; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index bdbf5cacaaf0..2f2d575bca29 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,10 +24,16 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) { + // SkPaint::antialiasing defaults to false, but + // SkFont::edging defaults to kAntiAlias. To keep them + // insync, we manually set the font to kAilas. + mFont.setEdging(SkFont::Edging::kAlias); +} Paint::Paint(const Paint& paint) : SkPaint(paint) + , mFont(paint.mFont) , mLetterSpacing(paint.mLetterSpacing) , mWordSpacing(paint.mWordSpacing) , mFontFeatureSettings(paint.mFontFeatureSettings) @@ -35,20 +41,17 @@ Paint::Paint(const Paint& paint) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) , mTypeface(paint.mTypeface) - , mAlign(paint.mAlign) {} + , mAlign(paint.mAlign) + , mStrikeThru(paint.mStrikeThru) + , mUnderline(paint.mUnderline) + , mDevKern(paint.mDevKern) {} -Paint::Paint(const SkPaint& paint) - : SkPaint(paint) - , mLetterSpacing(0) - , mWordSpacing(0) - , mFontFeatureSettings() - , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} Paint::~Paint() {} Paint& Paint::operator=(const Paint& other) { SkPaint::operator=(other); + mFont = other.mFont; mLetterSpacing = other.mLetterSpacing; mWordSpacing = other.mWordSpacing; mFontFeatureSettings = other.mFontFeatureSettings; @@ -57,15 +60,136 @@ Paint& Paint::operator=(const Paint& other) { mHyphenEdit = other.mHyphenEdit; mTypeface = other.mTypeface; mAlign = other.mAlign; + mStrikeThru = other.mStrikeThru; + mUnderline = other.mUnderline; + mDevKern = other.mDevKern; return *this; } bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && + a.mFont == b.mFont && a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && - a.mTypeface == b.mTypeface && a.mAlign == b.mAlign; + a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && + a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && + a.mDevKern == b.mDevKern; +} + +void Paint::reset() { + SkPaint::reset(); + + mFont = SkFont(); + mFont.setEdging(SkFont::Edging::kAlias); + + mStrikeThru = false; + mUnderline = false; + mDevKern = false; +} + +void Paint::setAntiAlias(bool aa) { + // Java does not support/understand subpixel(lcd) antialiasing + SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias); + // JavaPaint antialiasing affects both the SkPaint and SkFont settings. + SkPaint::setAntiAlias(aa); + mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); +} + +////////////////// Java flags compatibility ////////////////// + +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really + match up with skia anymore, so we have to do some shuffling in get/set flags() + + 3 flags apply to SkPaint (antialias, dither, filter -> enum) + 5 flags (merged with antialias) are for SkFont + 2 flags are for minikin::Paint (underline and strikethru) +*/ + +// flags relating to SkPaint +static const uint32_t sAntiAliasFlag = 0x01; // affects paint and font-edging +static const uint32_t sFilterBitmapFlag = 0x02; // maps to enum +static const uint32_t sDitherFlag = 0x04; +// flags relating to SkFont +static const uint32_t sFakeBoldFlag = 0x020; +static const uint32_t sLinearMetrics = 0x040; +static const uint32_t sSubpixelMetrics = 0x080; +static const uint32_t sEmbeddedBitmaps = 0x400; +static const uint32_t sForceAutoHinting = 0x800; +// flags related to minikin::Paint +static const uint32_t sUnderlineFlag = 0x08; +static const uint32_t sStrikeThruFlag = 0x10; +// flags no longer supported on native side (but mirrored for compatibility) +static const uint32_t sDevKernFlag = 0x100; + +static uint32_t paintToLegacyFlags(const SkPaint& paint) { + uint32_t flags = 0; + flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag; + flags |= -(int)paint.isDither() & sDitherFlag; + if (paint.getFilterQuality() != kNone_SkFilterQuality) { + flags |= sFilterBitmapFlag; + } + return flags; } + +static uint32_t fontToLegacyFlags(const SkFont& font) { + uint32_t flags = 0; + flags |= -(int)font.isEmbolden() & sFakeBoldFlag; + flags |= -(int)font.isLinearMetrics() & sLinearMetrics; + flags |= -(int)font.isSubpixel() & sSubpixelMetrics; + flags |= -(int)font.isEmbeddedBitmaps() & sEmbeddedBitmaps; + flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting; + return flags; +} + +static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) { + paint->setAntiAlias((flags & sAntiAliasFlag) != 0); + paint->setDither ((flags & sDitherFlag) != 0); + + if (flags & sFilterBitmapFlag) { + paint->setFilterQuality(kLow_SkFilterQuality); + } else { + paint->setFilterQuality(kNone_SkFilterQuality); + } +} + +static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) { + font->setEmbolden ((flags & sFakeBoldFlag) != 0); + font->setLinearMetrics ((flags & sLinearMetrics) != 0); + font->setSubpixel ((flags & sSubpixelMetrics) != 0); + font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0); + font->setForceAutoHinting((flags & sForceAutoHinting) != 0); + + if (flags & sAntiAliasFlag) { + font->setEdging(SkFont::Edging::kAntiAlias); + } else { + font->setEdging(SkFont::Edging::kAlias); + } +} + +uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) { + return paintToLegacyFlags(paint); +} + +void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) { + applyLegacyFlagsToPaint(flags, paint); +} + +uint32_t Paint::getJavaFlags() const { + uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont); + flags |= -(int)mStrikeThru & sStrikeThruFlag; + flags |= -(int)mUnderline & sUnderlineFlag; + flags |= -(int)mDevKern & sDevKernFlag; + return flags; +} + +void Paint::setJavaFlags(uint32_t flags) { + applyLegacyFlagsToPaint(flags, this); + applyLegacyFlagsToFont(flags, &mFont); + mStrikeThru = (flags & sStrikeThruFlag) != 0; + mUnderline = (flags & sUnderlineFlag) != 0; + mDevKern = (flags & sDevKernFlag) != 0; +} + } // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 60c805741058..a1b2b18195bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -138,6 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = fboSize.width(); info.height = fboSize.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear // the stencil and/or draw the functor. diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index c3563dbfd3cd..706325f00bd2 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -132,6 +132,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = mFBInfo.width(); info.height = mFBInfo.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); @@ -179,8 +180,8 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { canvas->resetMatrix(); auto functorImage = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr, - kBottomLeft_GrSurfaceOrigin); + reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, + canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin); canvas->drawImage(functorImage, 0, 0, &paint); canvas->restore(); } diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index 9e1bb8e8e548..501b8df9bc36 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_DRAW_GL_INFO_H #define ANDROID_HWUI_DRAW_GL_INFO_H +#include <SkColorSpace.h> + namespace android { namespace uirenderer { @@ -41,6 +43,9 @@ struct DrawGlInfo { // Input: current transform matrix, in OpenGL format float transform[16]; + // Input: Color space. + const SkColorSpace* color_space_ptr; + // Output: dirty region to redraw float dirtyLeft; float dirtyTop; diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 16a27598c56a..a9f651d38a06 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -78,24 +78,21 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( return layerUpdater; } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); uint32_t length = strlen(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); + canvas->drawText(utf16.get(), length, // text buffer 0, length, // draw range 0, length, // context range - x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */); + x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */); } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, nullptr); } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 6a1ca5a25361..e7124df72beb 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -278,10 +278,10 @@ public: static SkColor interpolateColor(float fraction, SkColor start, SkColor end); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path); static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str); diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index f0a5e9dff1b9..0795d13f441b 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -51,7 +51,7 @@ public: paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { - paint.setTextSize(10 + (frameNr % 20) + i * 20); + paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20); TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 58c99800875b..ecaaf487e4f8 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -16,7 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" - +#include "hwui/Paint.h" #include <SkGradientShader.h> class ListOfFadedTextAnimation; @@ -33,8 +33,8 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { canvas.drawColor(Color::White, SkBlendMode::kSrcOver); int length = dp(100); canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer); - SkPaint textPaint; - textPaint.setTextSize(dp(20)); + Paint textPaint; + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index 4111bd24847e..feb881f654f8 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" +#include "hwui/Paint.h" #include <SkFont.h> #include <cstdio> @@ -83,14 +84,14 @@ class ListViewAnimation : public TestListViewSceneBase { roundRectPaint.setColor(Color::White); canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); - SkPaint textPaint; + Paint textPaint; textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); - textPaint.setTextSize(dp(20)); + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); char buf[256]; snprintf(buf, sizeof(buf), "This card is #%d", cardId); TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25)); - textPaint.setTextSize(dp(15)); + textPaint.getSkFont().setSize(dp(15)); TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint, itemHeight, dp(45)); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index aa537b4f329c..f6cff1c643a1 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "renderthread/RenderProxy.h" #include "utils/Color.h" +#include "hwui/Paint.h" class MagnifierAnimation; @@ -37,9 +38,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 3befce4a395f..8630be87c09c 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -41,9 +41,9 @@ public: int top = bounds.fTop; mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); - mBluePaint.setTextSize(padding); + mBluePaint.getSkFont().setSize(padding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); - mGreenPaint.setTextSize(padding); + mGreenPaint.getSkFont().setSize(padding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a16b17849fc6..d30903679bce 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -15,6 +15,7 @@ */ #include "TestSceneBase.h" +#include "hwui/Paint.h" class TextAnimation; @@ -28,9 +29,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); for (int i = 0; i < 10; i++) { diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index 286f5f194aed..229c7f392629 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -17,6 +17,7 @@ #include "SkBlendMode.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" +#include "hwui/Paint.h" class TvApp; class TvAppNoRoundedCorner; @@ -116,13 +117,13 @@ private: [text, text2](RenderProperties& props, Canvas& canvas) { canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(24); + paint.getSkFont().setSize(24); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30); - paint.setTextSize(20); + paint.getSkFont().setSize(20); TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54); }); diff --git a/media/Android.bp b/media/Android.bp index 88ed9c6a05a9..0675a36f3e81 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -132,7 +132,6 @@ filegroup { "apex/java/android/media/Session2Command.java", "apex/java/android/media/Session2CommandGroup.java", "apex/java/android/media/Session2Link.java", - "apex/java/android/media/Session2Token.java", ], } diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java index 65b6f55a068a..45ea8261c6fb 100644 --- a/media/apex/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java @@ -24,7 +24,8 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; + static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN"; + static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 887b4475a4d1..41721f3011b1 100644 --- a/media/apex/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -20,9 +20,11 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; @@ -260,7 +262,8 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); + Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN); + Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); @@ -281,8 +284,7 @@ public class MediaController2 implements AutoCloseable { // Implementation for the local binder is no-op, // so can be used without worrying about deadlock. sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder); + mConnectedToken = token; } mCallbackExecutor.execute(() -> { mCallback.onConnected(MediaController2.this, allowedCommands); @@ -353,7 +355,7 @@ public class MediaController2 implements AutoCloseable { } private boolean requestConnectToSession() { - Session2Link sessionBinder = mSessionToken.getSessionLink(); + Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK); Bundle connectionRequest = createConnectionRequest(); try { sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); @@ -366,7 +368,7 @@ public class MediaController2 implements AutoCloseable { private boolean requestConnectToService() { // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE); + final Intent intent = new Intent(SESSION_SERVICE_INTERFACE); intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName()); // Use bindService() instead of startForegroundService() to start session service for three 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/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java index fdd07fdd52e3..80c91cc79c78 100644 --- a/media/apex/java/android/media/MediaSession2.java +++ b/media/apex/java/android/media/MediaSession2.java @@ -20,10 +20,10 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.Bundle; import android.os.Handler; -import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; @@ -108,8 +107,10 @@ public class MediaSession2 implements AutoCloseable { mCallbackExecutor = callbackExecutor; mCallback = callback; mSessionStub = new Session2Link(this); - mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), - mSessionStub); + + Bundle extras = new Bundle(); + extras.putParcelable(KEY_SESSION2_LINK, mSessionStub); + mSessionToken = new Session2Token(context, id, extras); mSessionManager = (MediaSessionManager) mContext.getSystemService( Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. @@ -141,6 +142,8 @@ public class MediaSession2 implements AutoCloseable { for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } + mSessionToken.destroy(); + mSessionManager.notifySession2Destroyed(mSessionToken); } catch (Exception e) { // Should not be here. } @@ -328,7 +331,7 @@ public class MediaSession2 implements AutoCloseable { // It's needed because we cannot call synchronous calls between // session/controller. Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); + connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java index 5bb746a7f9e3..f18cd317ef12 100644 --- a/media/apex/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,10 +47,6 @@ import java.util.Map; * for consistent behavior across all devices. */ public abstract class MediaSession2Service extends Service { - /** - * The {@link Intent} that must be declared as handled by the service. - */ - public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -100,7 +98,7 @@ public abstract class MediaSession2Service extends Service { @Override @Nullable public IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { + if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } diff --git a/media/java/android/media/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/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index 238cc2b8ee7d..80494ad1b2a6 100644 --- a/media/apex/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -19,13 +19,17 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.media.session.MediaSessionManager; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.text.TextUtils; import android.util.Log; @@ -35,7 +39,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. + * Represents an ongoing MediaSession2 or a MediaSession2Service. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -44,7 +48,7 @@ import java.util.Objects; * for consistent behavior across all devices. * <p> * This may be passed to apps by the session owner to allow them to create a - * {@link MediaController2} to communicate with the session. + * MediaController2 to communicate with the session. * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ @@ -64,6 +68,13 @@ public final class Session2Token implements Parcelable { }; /** + * The {@link Intent} that must be declared for the session service. + * @hide + */ + @SystemApi + public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + + /** * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -72,22 +83,26 @@ public final class Session2Token implements Parcelable { } /** - * Type for {@link MediaSession2}. + * Type for MediaSession2. */ public static final int TYPE_SESSION = 0; /** - * Type for {@link MediaSession2Service}. + * Type for MediaSession2Service. */ public static final int TYPE_SESSION_SERVICE = 1; + private final String mSessionId; + private final int mPid; private final int mUid; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; - private final Session2Link mSessionLink; private final ComponentName mComponentName; + private final Bundle mExtras; + + private boolean mDestroyed = false; /** * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. @@ -106,44 +121,67 @@ public final class Session2Token implements Parcelable { final PackageManager manager = context.getPackageManager(); final int uid = getUid(manager, serviceComponent.getPackageName()); - if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE, - serviceComponent)) { + if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) { Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service."); } + mSessionId = null; mComponentName = serviceComponent; mPackageName = serviceComponent.getPackageName(); mServiceName = serviceComponent.getClassName(); + mPid = -1; mUid = uid; mType = TYPE_SESSION_SERVICE; - mSessionLink = null; + mExtras = null; } - Session2Token(int uid, int type, String packageName, Session2Link sessionLink) { - mUid = uid; - mType = type; - mPackageName = packageName; + /** + * Constructor for the token with type {@link #TYPE_SESSION}. + * + * @param context The context. + * @param sessionId The ID of the session. Should be unique. + * @param extras The extras. + * @hide + */ + @SystemApi + public Session2Token(@NonNull Context context, @NonNull String sessionId, + @Nullable Bundle extras) { + if (sessionId == null) { + throw new IllegalArgumentException("sessionId shouldn't be null"); + } + if (context == null) { + throw new IllegalArgumentException("context shouldn't be null"); + } + mSessionId = sessionId; + mPid = Process.myPid(); + mUid = Process.myUid(); + mType = TYPE_SESSION; + mPackageName = context.getPackageName(); + mExtras = extras; mServiceName = null; mComponentName = null; - mSessionLink = sessionLink; } Session2Token(Parcel in) { + mSessionId = in.readString(); + mPid = in.readInt(); mUid = in.readInt(); mType = in.readInt(); mPackageName = in.readString(); mServiceName = in.readString(); - mSessionLink = in.readParcelable(null); mComponentName = ComponentName.unflattenFromString(in.readString()); + mExtras = in.readParcelable(null); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSessionId); + dest.writeInt(mPid); dest.writeInt(mUid); dest.writeInt(mType); dest.writeString(mPackageName); dest.writeString(mServiceName); - dest.writeParcelable(mSessionLink, flags); dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); + dest.writeParcelable(mExtras, flags); } @Override @@ -153,7 +191,7 @@ public final class Session2Token implements Parcelable { @Override public int hashCode() { - return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); + return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName); } @Override @@ -162,17 +200,27 @@ public final class Session2Token implements Parcelable { return false; } Session2Token other = (Session2Token) obj; - return mUid == other.mUid - && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName) + return TextUtils.equals(mSessionId, other.mSessionId) + && mPid == other.mPid + && mUid == other.mUid && mType == other.mType - && Objects.equals(mSessionLink, other.mSessionLink); + && TextUtils.equals(mPackageName, other.mPackageName) + && TextUtils.equals(mServiceName, other.mServiceName); } @Override public String toString() { return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; + + " service=" + mServiceName + "}"; + } + + /** + * @return pid of the session + * @hide + */ + @SystemApi + public int getPid() { + return mPid; } /** @@ -207,8 +255,36 @@ public final class Session2Token implements Parcelable { return mType; } - Session2Link getSessionLink() { - return mSessionLink; + /** + * @return extras + * @hide + */ + @SystemApi + @NonNull + public Bundle getExtras() { + return mExtras == null ? new Bundle() : new Bundle(mExtras); + } + + /** + * Destroys this session token. After this method is called, + * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called + * with this token. + * + * @see MediaSessionManager#notifySession2Created(Session2Token) + * @hide + */ + @SystemApi + public void destroy() { + mDestroyed = true; + } + + /** + * @return whether this token is destroyed + * @hide + */ + @SystemApi + public boolean isDestroyed() { + return mDestroyed; } private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface, diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index ed162504c553..fa6e03430315 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -37,6 +37,7 @@ interface ISessionManager { SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag, int userId); void notifySession2Created(in Session2Token sessionToken); + void notifySession2Destroyed(in Session2Token sessionToken); List<ControllerLink> getSessions(in ComponentName compName, int userId); List<Session2Token> getSession2Tokens(int userId); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index c64c452be3ef..cae4d1749287 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.IRemoteVolumeController; -import android.media.MediaSession2; import android.media.Session2Token; import android.os.Handler; import android.os.IBinder; @@ -115,11 +114,11 @@ public final class MediaSessionManager { } /** - * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is * created. * <p> * Do not use this API directly, but create a new instance through the - * {@link MediaSession2.Builder} instead. + * MediaSession2.Builder instead. * * @param token newly created session2 token */ @@ -130,6 +129,9 @@ public final class MediaSessionManager { if (token.getType() != Session2Token.TYPE_SESSION) { throw new IllegalArgumentException("token's type should be TYPE_SESSION"); } + if (token.isDestroyed()) { + throw new IllegalArgumentException("token is already destroyed"); + } try { mService.notifySession2Created(token); } catch (RemoteException e) { @@ -138,6 +140,31 @@ public final class MediaSessionManager { } /** + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is + * destroyed. + * <p> + * Do not use this API directly, but close a session with MediaSession2#close() instead. + * + * @param token destroyed session2 token + */ + public void notifySession2Destroyed(@NonNull Session2Token token) { + if (token == null) { + throw new IllegalArgumentException("token shouldn't be null"); + } + if (token.getType() != Session2Token.TYPE_SESSION) { + throw new IllegalArgumentException("token's type should be TYPE_SESSION"); + } + if (!token.isDestroyed()) { + throw new IllegalArgumentException("token should have been destroyed"); + } + try { + mService.notifySession2Destroyed(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Get a list of controllers for all ongoing sessions. The controllers will * be provided in priority order with the most important controller at index * 0. @@ -192,7 +219,7 @@ public final class MediaSessionManager { * current user. * <p> * Although this API can be used without any restriction, each session owners can accept or - * reject your uses of {@link MediaSession2}. + * reject your uses of MediaSession2. * * @return A list of {@link Session2Token}. */ 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/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h index 0490e650a7a4..e31ce195214f 100644 --- a/native/webview/plat_support/draw_fn.h +++ b/native/webview/plat_support/draw_fn.h @@ -20,7 +20,8 @@ extern "C" { // android to chromium are versioned. // // 1 is Android Q. This matches kAwDrawGLInfoVersion version 3. -static const int kAwDrawFnVersion = 1; +// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams. +static const int kAwDrawFnVersion = 2; struct AwDrawFn_OnSyncParams { int version; @@ -64,6 +65,16 @@ struct AwDrawFn_DrawGLParams { // Input: current transformation matrix in surface pixels. // Uses the column-based OpenGL matrix format. float transform[16]; + + // Input: Color space parameters. + float transfer_function_g; + float transfer_function_a; + float transfer_function_b; + float transfer_function_c; + float transfer_function_d; + float transfer_function_e; + float transfer_function_f; + float color_space_toXYZD50[9]; }; struct AwDrawFn_InitVkParams { diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp index b97bbc311624..afe103a25043 100644 --- a/native/webview/plat_support/draw_functor.cpp +++ b/native/webview/plat_support/draw_functor.cpp @@ -55,6 +55,8 @@ void onDestroyed(int functor, void* data) { void draw_gl(int functor, void* data, const uirenderer::DrawGlInfo& draw_gl_params) { + float gabcdef[7]; + draw_gl_params.color_space_ptr->transferFn(gabcdef); AwDrawFn_DrawGLParams params = { .version = kAwDrawFnVersion, .clip_left = draw_gl_params.clipLeft, @@ -64,12 +66,24 @@ void draw_gl(int functor, void* data, .width = draw_gl_params.width, .height = draw_gl_params.height, .is_layer = draw_gl_params.isLayer, + .transfer_function_g = gabcdef[0], + .transfer_function_a = gabcdef[1], + .transfer_function_b = gabcdef[2], + .transfer_function_c = gabcdef[3], + .transfer_function_d = gabcdef[4], + .transfer_function_e = gabcdef[5], + .transfer_function_f = gabcdef[6], }; COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform), mismatched_transform_matrix_sizes); for (int i = 0; i < NELEM(params.transform); ++i) { params.transform[i] = draw_gl_params.transform[i]; } + COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3), + gamut_transform_size_mismatch); + draw_gl_params.color_space_ptr->toXYZD50( + reinterpret_cast<skcms_Matrix3x3*>(¶ms.color_space_toXYZD50)); + SupportData* support = static_cast<SupportData*>(data); support->callbacks.draw_gl(functor, support->data, ¶ms); } diff --git a/packages/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/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index bc6e2fc7fc48..5acf4fbaa5cb 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -291,7 +291,7 @@ public class SmartActionsHelper { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages == null || messages.length == 0) { return Arrays.asList(new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) .build()); } @@ -310,7 +310,7 @@ public class SmartActionsHelper { break; } Person author = localUser != null && localUser.equals(senderPerson) - ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + ? ConversationActions.Message.PERSON_USER_SELF : senderPerson; extractMessages.push(new ConversationActions.Message.Builder(author) .setText(message.getText()) .setReferenceTime( diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 707349b0fd15..7f8127aa43a8 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -154,7 +154,7 @@ public class SmartActionHelperTest { ConversationActions.Message secondMessage = messages.get(0); MessageSubject.assertThat(secondMessage).hasText("secondMessage"); MessageSubject.assertThat(secondMessage) - .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + .hasPerson(ConversationActions.Message.PERSON_USER_SELF); MessageSubject.assertThat(secondMessage) .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 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/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index f21809fdbc1c..4ae044dec20b 100644 --- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -103,9 +103,7 @@ public class IpClientTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); - when(mContext.getSystemServiceName(ConnectivityManager.class)) - .thenReturn(Context.CONNECTIVITY_SERVICE); - when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); + when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java index 8b00ed053b26..00b3736f8d6b 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java @@ -23,6 +23,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; + import com.android.printspooler.R; /** @@ -366,7 +367,8 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis // and is janky. Now it is there but transparent, doing nothing. mEmbeddedContentScrim.setOnClickListener(null); mEmbeddedContentScrim.setClickable(false); - mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more); + mExpandCollapseIcon.setBackgroundResource( + com.android.internal.R.drawable.ic_expand_more); } else { if (mMoreOptionsButton.getVisibility() != View.GONE) { mMoreOptionsButton.setVisibility(View.VISIBLE); diff --git a/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml deleted file mode 100644 index 46b83094f9a8..000000000000 --- a/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2016 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/textColorSecondary"> - <path - android:fillColor="#FF000000" - android:pathData="M11,7h2v2h-2z"/> - <path - android:fillColor="#FF000000" - android:pathData="M11,11h2v6h-2z"/> - <path - android:fillColor="#FF000000" - android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8s8,3.59 8,8C20,16.41 16.41,20 12,20z"/> -</vector> diff --git a/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml b/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml deleted file mode 100644 index 44b18661ae60..000000000000 --- a/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 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. ---> -<vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:name="ic_rotate_to_landscape" - android:width="24dp" - android:height="24dp" - android:viewportHeight="24.0" - android:viewportWidth="24.0" - android:tint="?android:attr/colorControlNormal" > - <path - android:fillColor="#FF000000" - android:pathData="M12.72,23H11C5.49,23 1,18.51 1,13h2c0,3.78 2.63,6.95 6.15,7.79L8.13,19L9.87,18L12.72,23zM13,1h-1.72l2.85,5L15.87,5l-1.02,-1.79C18.37,4.05 21,7.22 21,11h2C23,5.49 18.51,1 13,1zM10.23,6L18,13.76L13.77,18L6,10.24L10.23,6C10.23,6 10.23,6 10.23,6M10.23,4C9.72,4 9.21,4.2 8.82,4.59L4.59,8.82c-0.78,0.78 -0.78,2.04 0,2.82l7.77,7.77c0.39,0.39 0.9,0.59 1.41,0.59c0.51,0 1.02,-0.2 1.41,-0.59l4.24,-4.24c0.78,-0.78 0.78,-2.04 0,-2.82l-7.77,-7.77C11.26,4.2 10.75,4 10.23,4L10.23,4z"/> -</vector> diff --git a/packages/SettingsLib/res/drawable/notification_auto_importance.xml b/packages/SettingsLib/res/drawable/notification_auto_importance.xml deleted file mode 100644 index c94615312306..000000000000 --- a/packages/SettingsLib/res/drawable/notification_auto_importance.xml +++ /dev/null @@ -1,27 +0,0 @@ -<!-- - Copyright (C) 2016 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M10.8,12.7l2.4,0l-1.2,-3.7z"/> - <path - android:fillColor="#FF000000" - android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM14.3,16l-0.7,-2h-3.2l-0.7,2H7.8L11,7h2l3.2,9H14.3z"/> -</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/layout/zen_mode_condition.xml b/packages/SettingsLib/res/layout/zen_mode_condition.xml index c85a8922a552..32221744e26f 100644 --- a/packages/SettingsLib/res/layout/zen_mode_condition.xml +++ b/packages/SettingsLib/res/layout/zen_mode_condition.xml @@ -67,7 +67,7 @@ android:layout_toStartOf="@android:id/button2" android:contentDescription="@string/accessibility_manual_zen_less_time" android:tint="?android:attr/colorAccent" - android:src="@drawable/ic_minus" /> + android:src="@*android:drawable/ic_minus" /> <ImageView android:id="@android:id/button2" @@ -79,6 +79,6 @@ android:layout_centerVertical="true" android:contentDescription="@string/accessibility_manual_zen_more_time" android:tint="?android:attr/colorAccent" - android:src="@drawable/ic_plus" /> + android:src="@*android:drawable/ic_plus" /> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index bae838749966..015f52cde789 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -24,7 +24,7 @@ <style name="BorderlessButton"> <item name="android:padding">12dp</item> - <item name="android:background">@drawable/btn_borderless_rect</item> + <item name="android:background">@*android:drawable/btn_borderless_rect</item> <item name="android:gravity">center</item> </style> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index c05915685e83..bed303078805 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -296,7 +296,7 @@ public class A2dpProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_headphones_a2dp; + return com.android.internal.R.drawable.ic_bt_headphones_a2dp; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index 873dd1a643a4..4ce9d3e2dff2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -205,7 +205,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_headphones_a2dp; + return com.android.internal.R.drawable.ic_bt_headphones_a2dp; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index ee80aa159ab1..a8a0b6df95c6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -61,13 +61,14 @@ public class BluetoothUtils { if (btClass != null) { switch (btClass.getMajorDeviceClass()) { case BluetoothClass.Device.Major.COMPUTER: - return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level, - iconScale), + return new Pair<>(getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_laptop, level, iconScale), context.getString(R.string.bluetooth_talkback_computer)); case BluetoothClass.Device.Major.PHONE: return new Pair<>( - getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level, + getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_cellphone, level, iconScale), context.getString(R.string.bluetooth_talkback_phone)); @@ -79,7 +80,8 @@ public class BluetoothUtils { case BluetoothClass.Device.Major.IMAGING: return new Pair<>( - getBluetoothDrawable(context, R.drawable.ic_settings_print, level, + getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_settings_print, level, iconScale), context.getString(R.string.bluetooth_talkback_imaging)); @@ -98,19 +100,22 @@ public class BluetoothUtils { if (btClass != null) { if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( - getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level, + getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_headset_hfp, level, iconScale), context.getString(R.string.bluetooth_talkback_headset)); } if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { return new Pair<>( - getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level, + getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_headphones_a2dp, level, iconScale), context.getString(R.string.bluetooth_talkback_headphone)); } } return new Pair<>( - getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale), + getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_settings_bluetooth, level, iconScale), context.getString(R.string.bluetooth_talkback_bluetooth)); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 6b6df9b928e5..a1bf93654774 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -226,7 +226,7 @@ public class HeadsetProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_headset_hfp; + return com.android.internal.R.drawable.ic_bt_headset_hfp; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 77dfbe994488..41c1d60ca551 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -228,7 +228,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_hearing_aid; + return com.android.internal.R.drawable.ic_bt_hearing_aid; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index c6bb2b304d6c..4bdbc3196556 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -211,7 +211,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { @Override public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_headset_hfp; + return com.android.internal.R.drawable.ic_bt_headset_hfp; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index 4dc050c6d5e3..35600b538d4d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -171,7 +171,7 @@ public class HidDeviceProfile implements LocalBluetoothProfile { @Override public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_misc_hid; + return com.android.internal.R.drawable.ic_bt_misc_hid; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index ca840d9a8ba4..7f906f6c5b06 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -165,7 +165,7 @@ public class HidProfile implements LocalBluetoothProfile { public int getDrawableResource(BluetoothClass btClass) { if (btClass == null) { - return R.drawable.ic_lockscreen_ime; + return com.android.internal.R.drawable.ic_lockscreen_ime; } return getHidClassDrawable(btClass); } @@ -174,11 +174,11 @@ public class HidProfile implements LocalBluetoothProfile { switch (btClass.getDeviceClass()) { case BluetoothClass.Device.PERIPHERAL_KEYBOARD: case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: - return R.drawable.ic_lockscreen_ime; + return com.android.internal.R.drawable.ic_lockscreen_ime; case BluetoothClass.Device.PERIPHERAL_POINTING: - return R.drawable.ic_bt_pointing_hid; + return com.android.internal.R.drawable.ic_bt_pointing_hid; default: - return R.drawable.ic_bt_misc_hid; + return com.android.internal.R.drawable.ic_bt_misc_hid; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 6acdcac86d92..0afc878bcc64 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -200,7 +200,7 @@ public final class MapClientProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index 28975d4b94c2..bea944cb323d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -196,7 +196,7 @@ public class MapProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 2d0a0902de24..6638592e8be5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -153,7 +153,7 @@ public class PanProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_network_pan; + return com.android.internal.R.drawable.ic_bt_network_pan; } // Tethering direction determines UI strings. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 46723937a941..4a27715d8681 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -192,7 +192,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index 1b3c453be4ed..165af2c751d4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -135,7 +135,7 @@ public class PbapServerProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index ea2ebde3ff4c..9e9c5b92fbfe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -196,7 +196,7 @@ final class SapProfile implements LocalBluetoothProfile { } public int getDrawableResource(BluetoothClass btClass) { - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } protected void finalize() { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index a5c6f0c28e78..6d0e3ac4b19c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -19,7 +19,6 @@ import android.bluetooth.BluetoothClass; import android.content.Context; import android.util.Log; -import com.android.settingslib.R; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -45,7 +44,7 @@ public class BluetoothMediaDevice extends MediaDevice { @Override public int getIcon() { //TODO(b/117129183): This is not final icon for bluetooth device, just for demo. - return R.drawable.ic_bt_headphones_a2dp; + return com.android.internal.R.drawable.ic_bt_headphones_a2dp; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 04f70cc676d9..99d9d1c71e0e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -20,8 +20,6 @@ import android.widget.Toast; import androidx.mediarouter.media.MediaRouter; -import com.android.settingslib.R; - /** * InfoMediaDevice extends MediaDevice to represents wifi device. */ @@ -45,7 +43,7 @@ public class InfoMediaDevice extends MediaDevice { @Override public int getIcon() { //TODO(b/121083246): This is not final icon for cast device, just for demo. - return R.drawable.ic_settings_print; + return com.android.internal.R.drawable.ic_settings_print; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index c808214dbcf4..01003aae6590 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -18,7 +18,6 @@ package com.android.settingslib.media; import android.content.Context; import android.util.Log; -import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -53,7 +52,7 @@ public class PhoneMediaDevice extends MediaDevice { @Override public int getIcon() { //TODO(b/117129183): This is not final icon for phone device, just for demo. - return R.drawable.ic_bt_cellphone; + return com.android.internal.R.drawable.ic_bt_cellphone; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java index f02044dc52e0..a106846fac26 100644 --- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java @@ -56,7 +56,7 @@ public class FooterPreference extends Preference { } private void init() { - setIcon(R.drawable.ic_info_outline_24dp); + setIcon(com.android.internal.R.drawable.ic_info_outline_24); setKey(KEY_FOOTER); setOrder(ORDER_FOOTER); setSelectable(false); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java index a436cb515bb8..cf7bcb282584 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java @@ -31,8 +31,6 @@ import android.graphics.PorterDuff.Mode; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.settingslib.R; - import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +43,7 @@ public class UserIconDrawableTest { public void getConstantState_shouldNotBeNull() { final Bitmap b = BitmapFactory.decodeResource( InstrumentationRegistry.getTargetContext().getResources(), - R.drawable.ic_mode_edit); + com.android.internal.R.drawable.ic_mode_edit); mDrawable = new UserIconDrawable(100 /* size */).setIcon(b).bake(); assertThat(mDrawable.getConstantState()).isNotNull(); } diff --git a/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml deleted file mode 100644 index 3fe1e9e5ca9f..000000000000 --- a/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2016 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/textColorSecondary"> - <path - android:fillColor="#000000" - android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> -</vector> diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 0eb6de9584eb..7a71551bb367 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import android.bluetooth.BluetoothDevice; import android.graphics.drawable.Drawable; -import com.android.settingslib.R; import com.android.settingslib.graph.BluetoothDeviceLayerDrawable; import org.junit.Test; @@ -34,7 +33,7 @@ public class BluetoothUtilsTest { @Test public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() { final Drawable drawable = BluetoothUtils.getBluetoothDrawable( - RuntimeEnvironment.application, R.drawable.ic_bt_laptop, + RuntimeEnvironment.application, com.android.internal.R.drawable.ic_bt_laptop, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, 1 /* iconScale */); assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class); @@ -43,7 +42,7 @@ public class BluetoothUtilsTest { @Test public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() { final Drawable drawable = BluetoothUtils.getBluetoothDrawable( - RuntimeEnvironment.application, R.drawable.ic_bt_laptop, + RuntimeEnvironment.application, com.android.internal.R.drawable.ic_bt_laptop, 10 /* batteryLevel */, 1 /* iconScale */); assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java index b77670bd01e5..491f32dd1185 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java @@ -12,8 +12,6 @@ import static com.google.common.truth.Truth.assertThat; import android.content.pm.ActivityInfo; import android.os.Bundle; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +29,7 @@ public class TileTest { mActivityInfo = new ActivityInfo(); mActivityInfo.packageName = RuntimeEnvironment.application.getPackageName(); mActivityInfo.name = "abc"; - mActivityInfo.icon = R.drawable.ic_plus; + mActivityInfo.icon = com.android.internal.R.drawable.ic_plus; mActivityInfo.metaData = new Bundle(); mTile = new Tile(mActivityInfo, "category"); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index bbb4249317f7..b379b54c605b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -53,8 +53,6 @@ import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Pair; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -255,7 +253,7 @@ public class TileUtilsTest { resolveInfo.activityInfo.metaData = new Bundle(oldMetadata); resolveInfo.activityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON, - R.drawable.ic_bt_cellphone); + com.android.internal.R.drawable.ic_bt_cellphone); outTiles.clear(); TileUtils.getTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java index 1b350cc83285..c6c2a4418357 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java @@ -31,7 +31,7 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class BluetoothDeviceLayerDrawableTest { - private static final int RES_ID = R.drawable.ic_bt_cellphone; + private static final int RES_ID = com.android.internal.R.drawable.ic_bt_cellphone; private static final int BATTERY_LEVEL = 15; private static final float BATTERY_ICON_SCALE = 0.75f; private static final int BATTERY_ICON_PADDING_TOP = 6; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java index 97de7ef2378a..e24b52594ba6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java @@ -74,10 +74,10 @@ public class ActionButtonsPreferenceTest { @Test public void onBindViewHolder_setIcon_shouldShowButtonByDefault() { - mPref.setButton1Icon(R.drawable.ic_plus); - mPref.setButton2Icon(R.drawable.ic_plus); - mPref.setButton3Icon(R.drawable.ic_plus); - mPref.setButton4Icon(R.drawable.ic_plus); + mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus); + mPref.setButton2Icon(com.android.internal.R.drawable.ic_plus); + mPref.setButton3Icon(com.android.internal.R.drawable.ic_plus); + mPref.setButton4Icon(com.android.internal.R.drawable.ic_plus); mPref.onBindViewHolder(mHolder); @@ -126,10 +126,10 @@ public class ActionButtonsPreferenceTest { @Test public void onBindViewHolder_setVisibleIsGoneAndSetIcon_shouldNotShowButton() { - mPref.setButton1Icon(R.drawable.ic_plus).setButton1Visible(false); - mPref.setButton2Icon(R.drawable.ic_plus).setButton2Visible(false); - mPref.setButton3Icon(R.drawable.ic_plus).setButton3Visible(false); - mPref.setButton4Icon(R.drawable.ic_plus).setButton4Visible(false); + mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus).setButton1Visible(false); + mPref.setButton2Icon(com.android.internal.R.drawable.ic_plus).setButton2Visible(false); + mPref.setButton3Icon(com.android.internal.R.drawable.ic_plus).setButton3Visible(false); + mPref.setButton4Icon(com.android.internal.R.drawable.ic_plus).setButton4Visible(false); mPref.onBindViewHolder(mHolder); @@ -215,7 +215,7 @@ public class ActionButtonsPreferenceTest { @Test public void onBindViewHolder_setButtonIcon_iconMustDisplayAboveText() { mPref.setButton1Text(R.string.install_other_apps); - mPref.setButton1Icon(R.drawable.ic_plus); + mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus); mPref.onBindViewHolder(mHolder); final Drawable[] drawablesAroundText = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java index 4c68c1476cff..63a958eeb5bb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java @@ -54,7 +54,7 @@ public class AppEntitiesHeaderControllerTest { mController = AppEntitiesHeaderController.newInstance(mContext, mAppEntitiesHeaderView); mAppEntityInfo = new AppEntityInfo.Builder() - .setIcon(mContext.getDrawable(R.drawable.ic_menu)) + .setIcon(mContext.getDrawable(com.android.internal.R.drawable.ic_menu)) .setTitle(TITLE) .setSummary(SUMMARY) .setOnClickListener(v -> { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java index cf6137d47bb0..c3ea336e0a4e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java @@ -58,7 +58,7 @@ public class BarChartPreferenceTest { mHolder = PreferenceViewHolder.createInstanceForTests(mBarChartView); mPreference = new BarChartPreference(mContext, null /* attrs */); - mIcon = mContext.getDrawable(R.drawable.ic_menu); + mIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_menu); mBarView1 = mBarChartView.findViewById(R.id.bar_view1); mBarView2 = mBarChartView.findViewById(R.id.bar_view2); mBarView3 = mBarChartView.findViewById(R.id.bar_view3); 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/Android.bp b/packages/SystemUI/Android.bp index 0a62b7c1bde2..e0d178fb9a1e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -57,6 +57,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "dagger2-2.19", @@ -108,6 +109,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png Binary files differnew file mode 100644 index 000000000000..c3a45e294e79 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png Binary files differnew file mode 100644 index 000000000000..e77c676bc13f --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md new file mode 100644 index 000000000000..a67b5e873b2e --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout.md @@ -0,0 +1,56 @@ +# Physics Animation Layout + +## Overview +**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively. + +An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen. + +## PhysicsAnimationController +PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events. + +### Configuration Methods + +The controller must override the following methods: + +```Set<ViewProperty> getAnimatedProperties()``` +Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations. + +```int getNextAnimationInChain(ViewProperty property, int index)``` +If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind. + +```float getOffsetForChainedPropertyAnimation(ViewProperty property)``` +Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc. + +```SpringForce getSpringForce(ViewProperty property)``` +Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed. + +### Animation Control Methods + +Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views. + +In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation. + +For example, moving the first child view to *(100, 200)*: + +``` +animateValueForChildAtIndex(TRANSLATION_X, 0, 100); +animateValueForChildAtIndex(TRANSLATION_Y, 0, 200); +``` + +This would use the physics animations constructed by the layout to spring the view to *(100, 200)*. + +If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```. + +## PhysicsAnimationLayout +The layout itself is a FrameLayout descendant with a few extra methods: + +```setController(PhysicsAnimationController controller)``` +Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods. + +```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)``` +Sets an end listener that is called when all animations on the given property have ended. + +```setMaxRenderedChildren(int max)``` +Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**. + +It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md new file mode 100644 index 000000000000..47354d45fa33 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-testing.md @@ -0,0 +1,11 @@ +# Physics Animation Testing +Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic. + +For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions. + +## Waiting for Animations to End +Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations. + +To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important. + +The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
\ No newline at end of file diff --git a/packages/SystemUI/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/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index 204408cda81f..13186fc6437c 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -22,8 +22,8 @@ <com.android.systemui.bubbles.BadgedImageView android:id="@+id/bubble_image" - android:layout_width="@dimen/bubble_size" - android:layout_height="@dimen/bubble_size" + android:layout_width="@dimen/individual_bubble_size" + android:layout_height="@dimen/individual_bubble_size" android:padding="@dimen/bubble_view_padding" android:clipToPadding="false"/> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml new file mode 100644 index 000000000000..e6f2376ae76b --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="bottom|center" + android:clipChildren="false" +> + + <LinearLayout + android:layout_height="290dp" + android:layout_width="412dp" + android:gravity="bottom" + android:padding="0dp" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:orientation="vertical" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + + <Space android:layout_width="match_parent" android:layout_height="2dp" + android:layout_weight="1" /> + + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:orientation="horizontal" + android:layoutDirection="rtl" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml new file mode 100644 index 000000000000..0c11cd977256 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="72dp" + android:layout_height="72dp" + android:gravity="center" + android:orientation="vertical" + android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_side_margin" + android:paddingEnd="4dip" + android:paddingStart="4dip"> + + <ImageView + android:id="@*android:id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:scaleType="center" + android:alpha="?android:attr/primaryContentAlpha" + /> + + <TextView + android:id="@*android:id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:paddingTop="10dp" + android:gravity="center" + android:textSize="12sp" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + <TextView + android:id="@*android:id/status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:gravity="center" + android:textColor="?android:attr/textColorTertiary" + android:textAppearance="?android:attr/textAppearanceSmall" + /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index abf9e056ed22..67e31ac7f9da 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -109,7 +109,7 @@ android:contentDescription="@string/accessibility_quick_settings_edit" android:focusable="true" android:padding="15dp" - android:src="@drawable/ic_mode_edit" + android:src="@*android:drawable/ic_mode_edit" android:tint="?android:attr/colorForeground"/> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ab0bbe10c37c..d435c67364ba 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -834,6 +834,18 @@ <dimen name="global_actions_panel_width">120dp</dimen> + <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen> + + <dimen name="global_actions_grid_side_margin">4dp</dimen> + <dimen name="global_actions_grid_separated_panel_width">104dp</dimen> + <dimen name="global_actions_grid_top_padding">8dp</dimen> + <dimen name="global_actions_grid_bottom_padding">8dp</dimen> + <dimen name="global_actions_grid_left_padding">4dp</dimen> + <dimen name="global_actions_grid_right_padding">4dp</dimen> + + <dimen name="global_actions_grid_item_side_margin">12dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen> + <dimen name="global_actions_top_padding">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> @@ -876,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 @@ -982,8 +996,8 @@ <dimen name="bubble_view_padding">0dp</dimen> <!-- Padding between bubbles when displayed in expanded state --> <dimen name="bubble_padding">8dp</dimen> - <!-- Size of the collapsed bubble --> - <dimen name="bubble_size">56dp</dimen> + <!-- Size of individual bubbles. --> + <dimen name="individual_bubble_size">56dp</dimen> <!-- How much to inset the icon in the circle --> <dimen name="bubble_icon_inset">16dp</dimen> <!-- Padding around the view displayed when the bubble is expanded --> @@ -1000,10 +1014,20 @@ <dimen name="bubble_expanded_header_height">48dp</dimen> <!-- Left and right padding applied to the header. --> <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen> + <!-- How far, horizontally, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_x_distance">100dp</dimen> + <!-- How far, vertically, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_y_distance">500dp</dimen> <!-- Max width of the message bubble--> <dimen name="bubble_message_max_width">144dp</dimen> <!-- Min width of the message bubble --> <dimen name="bubble_message_min_width">32dp</dimen> <!-- Interior padding of the message bubble --> <dimen name="bubble_message_padding">4dp</dimen> + <!-- Offset between bubbles in their stacked position. --> + <dimen name="bubble_stack_offset">5dp</dimen> + <!-- How far offscreen the bubble stack rests. --> + <dimen name="bubble_stack_offscreen">5dp</dimen> + <!-- How far down the screen the stack starts. --> + <dimen name="bubble_stack_starting_offset_y">100dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 5e32dd5b2c0e..6a6845742788 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -116,6 +116,14 @@ <item type="id" name="aod_mask_transition_progress_end_tag" /> <item type="id" name="aod_mask_transition_progress_start_tag" /> + <!-- For saving DynamicAnimation physics animations as view tags. --> + <item type="id" name="translation_x_dynamicanimation_tag"/> + <item type="id" name="translation_y_dynamicanimation_tag"/> + <item type="id" name="translation_z_dynamicanimation_tag"/> + <item type="id" name="alpha_dynamicanimation_tag"/> + <item type="id" name="scale_x_dynamicanimation_tag"/> + <item type="id" name="scale_y_dynamicanimation_tag"/> + <!-- Global Actions Menu --> <item type="id" name="global_actions_view" /> </resources> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index fd7a10500f36..e8fabf5a07f1 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -21,4 +21,10 @@ 0) as we can allow the carrier text to stretch as far as needed in the QS footer. --> <integer name="qs_footer_actions_width">-2</integer> <integer name="qs_footer_actions_weight">0</integer> + + <!-- Maximum number of bubbles to render and animate at one time. While the animations used are + lightweight translation animations, this number can be reduced on lower end devices if any + performance issues arise. --> + <integer name="bubbles_max_rendered">5</integer> + </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 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/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index 0c7a9a9fffd2..85265f458370 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -26,11 +26,11 @@ import android.widget.LinearLayout; * Layout class representing the Global Actions menu which appears when the power button is held. */ public abstract class MultiListLayout extends LinearLayout { - boolean mHasOutsideTouch; - boolean mHasSeparatedView; + protected boolean mHasOutsideTouch; + protected boolean mHasSeparatedView; - int mExpectedSeparatedItemCount; - int mExpectedListItemCount; + protected int mExpectedSeparatedItemCount; + protected int mExpectedListItemCount; public MultiListLayout(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 92d3cc1ae34f..36a813b914d5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -57,7 +57,7 @@ public class BadgedImageView extends ImageView { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setScaleType(ScaleType.CENTER_CROP); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size); mDotRenderer = new BadgeRenderer(mIconSize); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index a457deed7ba4..da29ab4e63d2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -18,9 +18,8 @@ package com.android.systemui.bubbles; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; @@ -229,10 +228,6 @@ public class BubbleController { } mStackView.stackDismissed(); - // Reset the position of the stack (TODO - or should we save / respect last user position?) - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - updateVisibility(); mNotificationEntryManager.updateNotifications(); } @@ -249,16 +244,14 @@ public class BubbleController { BubbleView bubble = mBubbles.get(notif.key); mStackView.updateBubble(bubble, notif, updatePosition); } else { - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { - setPosition = true; mStackView = new BubbleStackView(mContext); ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim // between bubble and the shade int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; sbv.addView(mStackView, bubblePosition, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } @@ -273,11 +266,6 @@ public class BubbleController { } mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); - if (setPosition) { - // Need to add the bubble to the stack before we can know the width - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - } } updateVisibility(); } @@ -423,24 +411,6 @@ public class BubbleController { return mStackView; } - // TODO: factor in PIP location / maybe last place user had it - /** - * Gets an appropriate starting point to position the bubble stack. - */ - private static Point getStartPoint(int size, Point displaySize) { - final int x = displaySize.x - size + EDGE_OVERLAP; - final int y = displaySize.y / 4; - return new Point(x, y); - } - - /** - * Gets an appropriate position for the bubble when the stack is expanded. - */ - static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { - // Same place for now.. - return new Point(EDGE_OVERLAP, size); - } - /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ @@ -458,7 +428,7 @@ public class BubbleController { entry.key).canBubble(); boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null && n.getNotification().getBubbleMetadata().getIntent() != null; - return hasOverlayIntent && canChannelOverlay && canAppOverlay; + return DEBUG_ENABLE_AUTO_BUBBLE && hasOverlayIntent && canChannelOverlay && canAppOverlay; } /** @@ -468,7 +438,8 @@ public class BubbleController { * message-like notification. * </p> */ - private boolean shouldAutoBubble(Context context, NotificationEntry entry) { + @VisibleForTesting + protected boolean shouldAutoBubble(Context context, NotificationEntry entry) { if (entry.isBubbleDismissed()) { return false; } @@ -495,9 +466,11 @@ public class BubbleController { Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle(); boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category); boolean isMessageStyle = Notification.MessagingStyle.class.equals(style); - return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages) + boolean shouldAutoBubble = + (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages) || (isImportantOngoing && autoBubbleOngoing) || autoBubbleAll; + return DEBUG_ENABLE_AUTO_BUBBLE && shouldAutoBubble; } private static boolean shouldAutoBubbleMessages(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java deleted file mode 100644 index c1063fa54ff2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; - -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.view.View; -import android.view.WindowManager; - -import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView; - -import java.util.Arrays; - -/** - * Math and animators to move bubbles around the screen. - * - * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip - * movements can be unified maybe? - */ -public class BubbleMovementHelper { - - private static final int MAGNET_ANIM_TIME = 150; - public static final int EDGE_OVERLAP = 0; - - private Context mContext; - private Point mDisplaySize; - - public BubbleMovementHelper(Context context) { - mContext = context; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplaySize = new Point(); - wm.getDefaultDisplay().getSize(mDisplaySize); - } - - /** - * @return the distance between the two provided points. - */ - static double distance(Point p1, Point p2) { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - } - - /** - * @return the y value of a line defined by y = mx+b - */ - static float findY(float m, float b, float x) { - return (m * x) + b; - } - - /** - * @return the x value of a line defined by y = mx+b - */ - static float findX(float m, float b, float y) { - return (y - b) / m; - } - - /** - * Determines a point on the edge of the screen based on the velocity and position. - */ - public Point getPointOnEdge(View bv, Point p, float velX, float velY) { - // Find the slope and the y-intercept - velX = velX == 0 ? 1 : velX; - final float m = velY / velX; - final float b = p.y - m * p.x; - - // There are two lines it can intersect, find the two points - Point pointHoriz = new Point(); - Point pointVert = new Point(); - - if (velX > 0) { - // right - pointHoriz.x = mDisplaySize.x; - pointHoriz.y = (int) findY(m, b, mDisplaySize.x); - } else { - // left - pointHoriz.x = EDGE_OVERLAP; - pointHoriz.y = (int) findY(m, b, 0); - } - if (velY > 0) { - // bottom - pointVert.x = (int) findX(m, b, mDisplaySize.y); - pointVert.y = mDisplaySize.y - getNavBarHeight(); - } else { - // top - pointVert.x = (int) findX(m, b, 0); - pointVert.y = EDGE_OVERLAP; - } - - // Use the point that's closest to the start position - final double distanceToVertPoint = distance(p, pointVert); - final double distanceToHorizPoint = distance(p, pointHoriz); - boolean useVert = distanceToVertPoint < distanceToHorizPoint; - // Check if we're being flung along the current edge, use opposite point in this case - // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but - // this works well enough for now - if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) { - // Flinging along left or right edge, favor vert edge - useVert = true; - - } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) { - // Flinging along top or bottom edge - useVert = false; - } - - if (useVert) { - pointVert.x = capX(pointVert.x, bv); - pointVert.y = capY(pointVert.y, bv); - return pointVert; - - } - pointHoriz.x = capX(pointHoriz.x, bv); - pointHoriz.y = capY(pointHoriz.y, bv); - return pointHoriz; - } - - /** - * @return whether the view is on a side edge of the screen (i.e. left or right). - */ - public boolean onSideEdge(View fv, Point p) { - return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x - - EDGE_OVERLAP - || p.x >= EDGE_OVERLAP; - } - - /** - * @return whether the view is on a top or bottom edge of the screen. - */ - public boolean onTopBotEdge(View bv, Point p) { - return p.y >= getStatusBarHeight() + EDGE_OVERLAP - || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y - - EDGE_OVERLAP; - } - - /** - * @return constrained x value based on screen size and how much a view can overlap with a side - * edge. - */ - public int capX(float x, View bv) { - // Floating things can't stick to top or bottom edges, so figure out if it's closer to - // left or right and just use that side + the overlap. - final float centerX = x + bv.getWidth() / 2; - if (centerX > mDisplaySize.x / 2) { - // Right side - return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP; - } else { - // Left side - return EDGE_OVERLAP; - } - } - - /** - * @return constrained y value based on screen size and how much a view can overlap with a top - * or bottom edge. - */ - public int capY(float y, View bv) { - final int height = bv.getHeight(); - if (y < getStatusBarHeight() + EDGE_OVERLAP) { - return getStatusBarHeight() + EDGE_OVERLAP; - } - if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) { - return mDisplaySize.y - height - EDGE_OVERLAP; - } - return (int) y; - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet animateMagnetTo(final BubbleStackView bv) { - Point pos = bv.getPosition(); - - // Find the distance to each edge - final int leftDistance = pos.x; - final int rightDistance = mDisplaySize.x - leftDistance; - final int topDistance = pos.y; - final int botDistance = mDisplaySize.y - topDistance; - - int smallest; - // Find the closest one - int[] distances = { - leftDistance, rightDistance, topDistance, botDistance - }; - Arrays.sort(distances); - smallest = distances[0]; - - // Animate to the closest edge - Point p = new Point(); - if (smallest == leftDistance) { - p.x = capX(EDGE_OVERLAP, bv); - p.y = capY(topDistance, bv); - } - if (smallest == rightDistance) { - p.x = capX(mDisplaySize.x, bv); - p.y = capY(topDistance, bv); - } - if (smallest == topDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(0, bv); - } - if (smallest == botDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(mDisplaySize.y, bv); - } - return getTranslateAnim(bv, p, MAGNET_ANIM_TIME); - } - - /** - * Animation to fling the provided view. - */ - public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) { - Point pos = bv.getPosition(); - Point endPos = getPointOnEdge(bv, pos, velX, velY); - endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv)); - final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2) - + Math.pow(endPos.y - pos.y, 2)); - final float sumVel = Math.abs(velX) + Math.abs(velY); - final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50); - return getTranslateAnim(bv, endPos, duration); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) { - return getTranslateAnim(v, p, duration, 0); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, - int duration, int startDelay) { - return getTranslateAnim(v, p, duration, startDelay, null); - } - - /** - * Animation to translate the provided view. - * - * @param v the view to translate. - * @param p the point to translate to. - * @param duration the duration of the animation. - * @param startDelay the start delay of the animation. - * @param listener the listener to add to the animation. - * - * @return the animation. - */ - public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration, - int startDelay, AnimatorListener listener) { - Point curPos = v.getPosition(); - final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x); - animX.setDuration(duration); - animX.setStartDelay(startDelay); - animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionX((int) value); - } - }); - - final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y); - animY.setDuration(duration); - animY.setStartDelay(startDelay); - animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionY((int) value); - } - }); - if (listener != null) { - animY.addListener(listener); - } - - AnimatorSet set = new AnimatorSet(); - set.playTogether(animX, animY); - set.setInterpolator(FAST_OUT_SLOW_IN); - return set; - } - - - // TODO -- now that this is in system we should be able to get these better, but ultimately - // makes more sense to move to movement bounds style a la PIP - /** - * Returns the status bar height. - */ - public int getStatusBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } - - /** - * Returns the status bar height. - */ - public int getNavBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dcd121bdb239..b584f6781796 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -16,59 +16,89 @@ package com.android.systemui.bubbles; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.app.ActivityView; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.ExpandedAnimationController; +import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; +import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; -import com.android.systemui.statusbar.notification.stack.ViewState; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { - private static final String TAG = "BubbleStackView"; + + /** + * Friction applied to fling animations. Since the stack must land on one of the sides of the + * screen, we want less friction horizontally so that the stack has a better chance of making it + * to the side without needing a spring. + */ + private static final float FLING_FRICTION_X = 1.15f; + private static final float FLING_FRICTION_Y = 1.5f; + + /** + * Damping ratio to use for the stack spring animation used to spring the stack to its final + * position after a fling. + */ + private static final float SPRING_DAMPING_RATIO = 0.85f; + + /** + * Minimum fling velocity required to trigger moving the stack from one side of the screen to + * the other. + */ + private static final float ESCAPE_VELOCITY = 750f; + private Point mDisplaySize; - private FrameLayout mBubbleContainer; + private final SpringAnimation mExpandedViewXAnim; + private final SpringAnimation mExpandedViewYAnim; + + private PhysicsAnimationLayout mBubbleContainer; + private StackAnimationController mStackAnimationController; + private ExpandedAnimationController mExpandedAnimationController; + private BubbleExpandedViewContainer mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; + private int mExpandedAnimateXDistance; + private int mExpandedAnimateYDistance; private boolean mIsExpanded; private int mExpandedBubbleHeight; private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; - private Point mCollapsedPosition; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -110,8 +140,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F setOnTouchListener(mTouchHandler); Resources res = getResources(); - mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedAnimateXDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); + mExpandedAnimateYDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); @@ -120,6 +154,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + + mStackAnimationController = new StackAnimationController(); + mExpandedAnimationController = new ExpandedAnimationController(); + + mBubbleContainer = new PhysicsAnimationLayout(context); + mBubbleContainer.setMaxRenderedChildren( + getResources().getInteger(R.integer.bubbles_max_rendered)); + mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setElevation(elevation); + mBubbleContainer.setPadding(padding, 0, padding, 0); + mBubbleContainer.setClipChildren(false); + addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mExpandedViewContainer = (BubbleExpandedViewContainer) LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, this /* parent */, false /* attachToRoot */); @@ -128,11 +175,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); - mBubbleContainer = new FrameLayout(context); - mBubbleContainer.setElevation(elevation); - mBubbleContainer.setPadding(padding, 0, padding, 0); - mBubbleContainer.setClipChildren(false); - addView(mBubbleContainer); + mExpandedViewXAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); + mExpandedViewXAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + + mExpandedViewYAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); + mExpandedViewYAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); setClipChildren(false); } @@ -144,38 +199,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } @Override - public void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - - int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - if (mIsExpanded) { - ViewGroup parent = (ViewGroup) getParent(); - int parentWidth = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY); - int parentHeight = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec); - - int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight); - setMeasuredDimension(widthSpec, parentHeight); - } else { - // Not expanded - measureChild(mExpandedViewContainer, 0, 0); - - // Bubbles are translated a little to stack on top of each other - widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec); - - heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(), - MeasureSpec.EXACTLY); - setMeasuredDimension(widthSpec, heightSpec); - } - } - - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getRawX(); float y = ev.getRawY(); @@ -293,9 +316,11 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F boolean updatePosition) { bubbleView.update(entry); if (updatePosition && !mIsExpanded) { - // If alerting it gets promoted to top of the stack - mBubbleContainer.removeView(bubbleView); - mBubbleContainer.addView(bubbleView, 0); + // If alerting it gets promoted to top of the stack. + if (mBubbleContainer.indexOfChild(bubbleView) != 0) { + mBubbleContainer.removeViewAndThen(bubbleView, + () -> mBubbleContainer.addView(bubbleView, 0)); + } requestUpdate(); } if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { @@ -359,36 +384,51 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; updateExpandedBubble(); + applyCurrentState(); + //requestUpdate(); + + mIsAnimating = true; + + Runnable updateAfter = () -> { + applyCurrentState(); + mIsAnimating = false; + requestUpdate(); + }; if (shouldExpand) { - // Save current position so that we might return there - savePosition(); + mBubbleContainer.setController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack( + mStackAnimationController.getStackPosition(), updateAfter); + } else { + mBubbleContainer.cancelAllAnimations(); + mExpandedAnimationController.collapseBackToStack( + () -> { + mBubbleContainer.setController(mStackAnimationController); + updateAfter.run(); + }); } - // Determine the translation for the stack - Point position = shouldExpand - ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize) - : mCollapsedPosition; - int delay = shouldExpand ? 0 : 100; - AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position, - 200, delay, null); - if (!shouldExpand) { - // First collapse the stack, then translate, maybe should expand at same time? - animateStackExpansion(() -> translationAnim.start()); - } else { - // First translate, then expand - translationAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mIsAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - animateStackExpansion(() -> mIsAnimating = false); - } - }); - translationAnim.start(); + final float xStart = + mStackAnimationController.getStackPosition().x < getWidth() / 2 + ? -mExpandedAnimateXDistance + : mExpandedAnimateXDistance; + + final float yStart = Math.min( + mStackAnimationController.getStackPosition().y, + mExpandedAnimateYDistance); + final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding; + + if (shouldExpand) { + mExpandedViewContainer.setTranslationX(xStart); + mExpandedViewContainer.setTranslationY(yStart); + mExpandedViewContainer.setAlpha(0f); } + + mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart); + mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart); + mExpandedViewContainer.animate() + .setDuration(100) + .alpha(shouldExpand ? 1f : 0f); } } @@ -401,14 +441,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F + mBubbleContainer.getPaddingStart(); } - /** - * Saves the current position of the stack, used to save user placement of the stack to - * return to after an animation. - */ - private void savePosition() { - mCollapsedPosition = getPosition(); - } - private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { if (mExpandListener != null) { NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; @@ -420,31 +452,154 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F return getBubbleAt(0); } - private BubbleView getBubbleAt(int i) { + /** Return the BubbleView at the given index from the bubble container. */ + public BubbleView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i ? (BubbleView) mBubbleContainer.getChildAt(i) : null; } @Override - public void setPosition(int x, int y) { - setPositionX(x); - setPositionY(y); + public void setPosition(float x, float y) { + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); } @Override - public void setPositionX(int x) { - setTranslationX(x); + public void setPositionX(float x) { + // Unsupported, use setPosition(x, y). } @Override - public void setPositionY(int y) { - setTranslationY(y); + public void setPositionY(float y) { + // Unsupported, use setPosition(x, y). } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return mStackAnimationController.getStackPosition(); + } + + /** Called when a drag operation on an individual bubble has started. */ + public void onBubbleDragStart(BubbleView bubble) { + // TODO: Save position and snap back if not dismissed. + } + + /** Called with the coordinates to which an individual bubble has been dragged. */ + public void onBubbleDragged(BubbleView bubble, float x, float y) { + bubble.setTranslationX(x); + bubble.setTranslationY(y); + } + + /** Called when a drag operation on an individual bubble has finished. */ + public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + } + + void onDragStart() { + if (mIsExpanded) { + return; + } + + mStackAnimationController.cancelStackPositionAnimations(); + mBubbleContainer.setController(mStackAnimationController); + mIsAnimating = false; + } + + void onDragged(float x, float y) { + // TODO: We can drag if animating - just need to reroute inflight anims to drag point. + if (mIsExpanded) { + return; + } + + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); + } + + void onDragFinish(float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + + if (mIsExpanded || mIsAnimating) { + return; + } + + final boolean stackOnLeftSide = x + - mBubbleContainer.getChildAt(0).getWidth() / 2 + < mDisplaySize.x / 2; + + final boolean stackShouldFlingLeft = stackOnLeftSide + ? velX < ESCAPE_VELOCITY + : velX < -ESCAPE_VELOCITY; + + final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion(); + + // Target X translation (either the left or right side of the screen). + final float destinationRelativeX = stackShouldFlingLeft + ? stackBounds.left : stackBounds.right; + + // Minimum velocity required for the stack to make it to the side of the screen. + final float escapeVelocity = getMinXVelocity( + x, + destinationRelativeX, + FLING_FRICTION_X); + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so + // that it'll make it all the way to the side of the screen. + final float startXVelocity = stackShouldFlingLeft + ? Math.min(escapeVelocity, velX) + : Math.max(escapeVelocity, velX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + startXVelocity, + FLING_FRICTION_X, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + destinationRelativeX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + velY, + FLING_FRICTION_Y, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + /* destination */ null); + } + + /** + * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a + * given frictional force. + * + * This is not derived using real math, I just made it up because the math in FlingAnimation + * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it + * to the edge via Fling, it'll get Spring'd there anyway. + * + * TODO(tsuji, or someone who likes math): Figure out math. + */ + private float getMinXVelocity(float x, float destX, float friction) { + return (destX - x) * (friction * 5) + ESCAPE_VELOCITY; + } + + @Override + public void getBoundsOnScreen(Rect outRect) { + if (!mIsExpanded) { + mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); + } else { + mBubbleContainer.getBoundsOnScreen(outRect); + } + } + + private int getStatusBarHeight() { + if (getRootWindowInsets() != null) { + WindowInsets insets = getRootWindowInsets(); + return Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + } + + return 0; } private boolean isIntersecting(View view, float x, float y) { @@ -478,22 +633,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); mExpandedViewContainer.setExpandedView(expandedView); - expandedView.setCallback(new ActivityView.StateCallback() { - @Override - public void onActivityViewReady(ActivityView view) { - Log.d(TAG, "onActivityViewReady(" - + mExpandedBubble.getEntry().key + "): " + view); - view.startActivity(intent); - } - - @Override - public void onActivityViewDestroyed(ActivityView view) { - NotificationEntry entry = mExpandedBubble != null - ? mExpandedBubble.getEntry() : null; - Log.d(TAG, "onActivityViewDestroyed(key=" - + ((entry != null) ? entry.key : "(none)") + "): " + view); - } - }); } else { // Bubble with notification view expanded state ExpandableNotificationRow row = mExpandedBubble.getRowView(); @@ -510,9 +649,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setHeaderText(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); + float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition((int) pointerPosition); } private void applyCurrentState() { @@ -522,7 +660,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { - mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); View expandedView = mExpandedViewContainer.getExpandedView(); if (expandedView instanceof ActivityView) { if (expandedView.isAttachedToWindow()) { @@ -537,53 +674,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(); bv.setZ(bubbsCount - i); - - int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; - ViewState viewState = new ViewState(); - viewState.initFrom(bv); - viewState.xTranslation = transX; - viewState.applyToView(bv); - - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - bv.setTag(new Point(transX, 0)); - } - } - } - - private void animateStackExpansion(Runnable endRunnable) { - int childCount = mBubbleContainer.getChildCount(); - for (int i = 0; i < childCount; i++) { - BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i); - int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i; - int duration = childCount > 1 ? 200 : 0; - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - child.setTag(new Point(transX, 0)); - } - ViewPropertyAnimator anim = child - .animate() - .setStartDelay(15 * i) - .setDuration(duration) - .setInterpolator(mIsExpanded - ? new OvershootInterpolator() - : new AccelerateInterpolator()) - .translationY(0) - .translationX(transX); - final int fi = i; - // Probably want this choreographed with translation somehow / make it snappier - anim.withStartAction(() -> mIsAnimating = true); - anim.withEndAction(() -> { - if (endRunnable != null) { - endRunnable.run(); - } - if (fi == mBubbleContainer.getChildCount() - 1) { - applyCurrentState(); - mIsAnimating = false; - requestUpdate(); - } - }); - anim.start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 97784b0f4f93..22cd2fcc3e72 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -19,7 +19,7 @@ package com.android.systemui.bubbles; import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY; import android.content.Context; -import android.graphics.Point; +import android.graphics.PointF; import android.os.Handler; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -37,18 +37,16 @@ class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; - private BubbleMovementHelper mMovementHelper; // The position of the bubble on down event - private int mBubbleDownPosX; - private int mBubbleDownPosY; + private float mBubbleDownPosX; + private float mBubbleDownPosY; // The touch position on down event - private int mDownX = -1; - private int mDownY = -1; + private float mDownX = -1; + private float mDownY = -1; private boolean mMovedEnough; private int mTouchSlopSquared; - private float mMinFlingVelocity; private VelocityTracker mVelocityTracker; private boolean mInDismissTarget; @@ -71,32 +69,27 @@ class BubbleTouchHandler implements View.OnTouchListener { /** * Sets the position of the view. */ - void setPosition(int x, int y); + void setPosition(float x, float y); /** * Sets the x position of the view. */ - void setPositionX(int x); + void setPositionX(float x); /** * Sets the y position of the view. */ - void setPositionY(int y); + void setPositionY(float y); /** * @return the position of the view. */ - Point getPosition(); + PointF getPosition(); } public BubbleTouchHandler(Context context) { final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mTouchSlopSquared = touchSlop * touchSlop; - - // Multiply by 3 for better fling - mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3; - - mMovementHelper = new BubbleMovementHelper(context); mDismissViewController = new PipDismissViewController(context); } @@ -119,9 +112,11 @@ class BubbleTouchHandler implements View.OnTouchListener { FloatingView floatingView = (FloatingView) targetView; boolean isBubbleStack = floatingView instanceof BubbleStackView; - Point startPos = floatingView.getPosition(); - int rawX = (int) event.getRawX(); - int rawY = (int) event.getRawY(); + PointF startPos = floatingView.getPosition(); + float rawX = event.getRawX(); + float rawY = event.getRawY(); + float x = mBubbleDownPosX + rawX - mDownX; + float y = mBubbleDownPosY + rawY - mDownY; switch (action) { case MotionEvent.ACTION_DOWN: trackMovement(event); @@ -134,6 +129,13 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; mMovedEnough = false; + + if (isBubbleStack) { + stack.onDragStart(); + } else { + stack.onBubbleDragStart((BubbleView) floatingView); + } + break; case MotionEvent.ACTION_MOVE: @@ -145,22 +147,23 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; } - final int deltaX = rawX - mDownX; - final int deltaY = rawY - mDownY; + final float deltaX = rawX - mDownX; + final float deltaY = rawY - mDownY; if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) { mMovedEnough = true; } - int x = mBubbleDownPosX + rawX - mDownX; - int y = mBubbleDownPosY + rawY - mDownY; if (mMovedEnough) { - if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) { + if (floatingView instanceof BubbleView) { mBubbleDraggingOut = ((BubbleView) floatingView); + stack.onBubbleDragged(mBubbleDraggingOut, x, y); + } else { + stack.onDragged(x, y); } - floatingView.setPosition(x, y); } // TODO - when we're in the target stick to it / animate in some way? - mInDismissTarget = mDismissViewController.updateTarget((View) floatingView); + mInDismissTarget = mDismissViewController.updateTarget( + isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView); break; case MotionEvent.ACTION_CANCEL: @@ -181,19 +184,9 @@ class BubbleTouchHandler implements View.OnTouchListener { final float velX = mVelocityTracker.getXVelocity(); final float velY = mVelocityTracker.getYVelocity(); if (isBubbleStack) { - if ((Math.abs(velY) > mMinFlingVelocity) - || (Math.abs(velX) > mMinFlingVelocity)) { - // It's being flung somewhere - mMovementHelper.animateFlingTo(stack, velX, velY).start(); - } else { - // Magnet back to nearest edge - mMovementHelper.animateMagnetTo(stack).start(); - } + stack.onDragFinish(x, y, velX, velY); } else { - // Individual bubble got dragged but not dismissed.. lets animate it back - // into position - Point toGoTo = (Point) ((View) floatingView).getTag(); - mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); + stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY); } } else if (floatingView.equals(stack.getExpandedBubble())) { stack.collapseStack(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index e8432b9a7017..dc948325e27a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -22,7 +22,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -60,6 +60,8 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati private NotificationEntry mEntry; private PendingIntent mAppOverlayIntent; private ActivityView mActivityView; + private boolean mActivityViewReady; + private boolean mActivityViewStarted; public BubbleView(Context context) { this(context, null); @@ -192,10 +194,10 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati fraction = showDot ? fraction : 1 - fraction; mBadgedImageView.setDotScale(fraction); }).withEndAction(() -> { - if (!showDot) { - mBadgedImageView.setShowDot(false); - } - }).start(); + if (!showDot) { + mBadgedImageView.setShowDot(false); + } + }).start(); } } @@ -232,8 +234,21 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati */ public ActivityView getActivityView() { if (mActivityView == null) { - mActivityView = new ActivityView(mContext); + mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, + true /* singleTaskInstance */); Log.d(TAG, "[getActivityView] created: " + mActivityView); + mActivityView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + mActivityViewReady = true; + mActivityView.startActivity(mAppOverlayIntent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + mActivityViewReady = false; + } + }); } return mActivityView; } @@ -245,46 +260,44 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati if (mActivityView == null) { return; } - // HACK: Only release if initialized. There's no way to know if the ActivityView has - // been initialized. Calling release() if it hasn't been initialized will crash. - + if (!mActivityViewReady) { + // release not needed, never initialized? + mActivityView = null; + return; + } + // HACK: release() will crash if the view is not attached. if (!mActivityView.isAttachedToWindow()) { - // HACK: release() will crash if the view is not attached. - mActivityView.setVisibility(View.GONE); tmpParent.addView(mActivityView, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } - try { - mActivityView.release(); - } catch (IllegalStateException ex) { - Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); - } + + mActivityView.release(); ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); mActivityView = null; } @Override - public void setPosition(int x, int y) { + public void setPosition(float x, float y) { setPositionX(x); setPositionY(y); } @Override - public void setPositionX(int x) { + public void setPositionX(float x) { setTranslationX(x); } @Override - public void setPositionY(int y) { + public void setPositionY(float y) { setTranslationY(y); } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return new PointF(getTranslationX(), getTranslationY()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java new file mode 100644 index 000000000000..4f870f6ceffc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.graphics.PointF; +import android.view.View; +import android.view.WindowInsets; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their expanded state, or animating to/from the + * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be + * dismissed. + */ +public class ExpandedAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + + /** + * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack} + * and used to return to stack form in {@link #collapseBackToStack}. + */ + private PointF mExpandedFrom; + + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ + private float mStackOffsetPx; + /** Spacing between bubbles in the expanded state. */ + private float mBubblePaddingPx; + /** Size of each bubble. */ + private float mBubbleSizePx; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + mStackOffsetPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_stack_offset); + mBubblePaddingPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_padding); + mBubbleSizePx = layout.getResources().getDimensionPixelSize( + R.dimen.individual_bubble_size); + } + + /** + * Animates expanding the bubbles into a row along the top of the screen. + * + * @return The y-value to which the bubbles were expanded, in case that's useful. + */ + public float expandFromStack(PointF expandedFrom, Runnable after) { + mExpandedFrom = expandedFrom; + + // How much to translate the next bubble, so that it is not overlapping the previous one. + float translateNextBubbleXBy = mBubblePaddingPx; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY()); + translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx; + } + + runAfterTranslationsEnd(after); + return getExpandedY(); + } + + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(Runnable after) { + // Stack to the left if we're going to the left, or right if not. + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex( + i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y); + } + + runAfterTranslationsEnd(after); + } + + /** The Y value of the row of expanded bubbles. */ + private float getExpandedY() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + if (insets != null) { + return mBubblePaddingPx + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + } + + return mBubblePaddingPx; + } + + /** Runs the given Runnable after all translation-related animations have ended. */ + private void runAfterTranslationsEnd(Runnable after) { + DynamicAnimation.OnAnimationEndListener allEndedListener = + (animation, canceled, value, velocity) -> { + if (!mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y)) { + after.run(); + } + }; + + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y); + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return 0; + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + @Override + void onChildAdded(View child, int index) { + // TODO: Animate the new bubble into the row, and push the other bubbles out of the way. + child.setTranslationY(getExpandedY()); + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // TODO: Animate the bubble out, and pull the other bubbles into its position. + actuallyRemove.run(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java new file mode 100644 index 000000000000..4e0abc8009b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +/** + * End listener that removes itself from its animation when called for the first time. Useful since + * anonymous OnAnimationEndListener instances can't pass themselves to + * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass + * implementation. + */ +public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener { + + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + animation.removeEndListener(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java new file mode 100644 index 000000000000..1ced3a4daac6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * Layout that constructs physics-based animations for each of its children, which behave according + * to settings provided by a {@link PhysicsAnimationController} instance. + * + * See physics-animation-layout.md. + */ +public class PhysicsAnimationLayout extends FrameLayout { + private static final String TAG = "Bubbs.PAL"; + + /** + * Controls the construction, configuration, and use of the physics animations supplied by this + * layout. + */ + abstract static class PhysicsAnimationController { + + /** + * Constant to return from {@link #getNextAnimationInChain} if the animation should not be + * chained at all. + */ + protected static final int NONE = -1; + + /** Set of properties for which the layout should construct physics animations. */ + abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties(); + + /** + * Returns the index of the next animation after the given index in the animation chain, or + * {@link #NONE} if it should not be chained, or if the chain should end at the given index. + * + * If a next index is returned, an update listener will be added to the animation at the + * given index that dispatches value updates to the animation at the next index. This + * creates a 'following' effect. + * + * Typical implementations of this method will return either index + 1, or index - 1, to + * create forward or backward chains between adjacent child views, but this is not required. + */ + abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index); + + /** + * Offsets to be added to the value that chained animations of the given property dispatch + * to subsequent child animations. + * + * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles + * stack off to the left or right side slightly. + */ + abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property); + + /** + * Returns the SpringForce to be used for the given child view's property animation. Despite + * these usually being similar or identical across properties and views, {@link SpringForce} + * also contains the SpringAnimation's final position, so we have to construct a new one for + * each animation rather than using a constant. + */ + abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view); + + /** + * Called when a new child is added at the specified index. Controllers can use this + * opportunity to animate in the new view. + */ + abstract void onChildAdded(View child, int index); + + /** + * Called when a child is to be removed from the layout. Controllers can use this + * opportunity to animate out the new view before calling the provided callback to actually + * remove it. + * + * Controllers should be careful to ensure that actuallyRemove is called on all code paths + * or child views will never be removed. + */ + abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove); + + protected PhysicsAnimationLayout mLayout; + + PhysicsAnimationController() { } + + protected void setLayout(PhysicsAnimationLayout layout) { + this.mLayout = layout; + } + + protected PhysicsAnimationLayout getLayout() { + return mLayout; + } + } + + /** + * End listeners that are called when every child's animation of the given property has + * finished. + */ + protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> + mEndListenerForProperty = new HashMap<>(); + + /** + * List of views that were passed to removeView, but are currently being animated out. These + * views will be actually removed by the controller (via super.removeView) once they're done + * animating out. + */ + private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); + + /** The currently active animation controller. */ + private PhysicsAnimationController mController; + + /** + * The maximum number of children to render and animate at a time. See + * {@link #setMaxRenderedChildren}. + */ + private int mMaxRenderedChildren = 5; + + public PhysicsAnimationLayout(Context context) { + super(context); + } + + /** + * The maximum number of children to render and animate at a time. Any child views added beyond + * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, + * the corresponding property will be set with no animation. + */ + public void setMaxRenderedChildren(int max) { + this.mMaxRenderedChildren = max; + } + + /** + * Sets the animation controller and constructs or reconfigures the layout's physics animations + * to meet the controller's specifications. + */ + public void setController(PhysicsAnimationController controller) { + cancelAllAnimations(); + mEndListenerForProperty.clear(); + + this.mController = controller; + mController.setLayout(this); + + // Set up animations for this controller's animated properties. + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationsForProperty(property); + } + } + + /** + * Sets an end listener that will be called when all child animations for a given property have + * stopped running. + */ + public void setEndListenerForProperty( + DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.put(property, listener); + } + + /** + * Removes the end listener that would have been called when all child animations for a given + * property stopped running. + */ + public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.remove(property); + } + + /** + * Returns the index of the view that precedes the given index, ignoring views that were passed + * to removeView, but are currently being animated out before actually being removed. + * + * @return index of the preceding view, or -1 if there are none. + */ + public int getPrecedingNonRemovedViewIndex(int index) { + for (int i = index + 1; i < getChildCount(); i++) { + View precedingView = getChildAt(i); + if (!mViewsToBeActuallyRemoved.contains(precedingView)) { + return i; + } + } + + return -1; + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + setChildrenVisibility(); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setController is called. + if (mController != null) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + @Override + public void removeView(View view) { + removeViewAndThen(view, /* callback */ null); + } + + /** + * Let the controller know that this view should be removed, and then call the callback once the + * controller has finished any removal animations and the view has actually been removed. + */ + public void removeViewAndThen(View view, Runnable callback) { + if (mController != null) { + final int index = indexOfChild(view); + // Remove the view only if it exists in this layout, and we're not already working on + // animating its removal. + if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { + mViewsToBeActuallyRemoved.add(view); + setChildrenVisibility(); + + // Tell the controller to animate this view out, and call the callback when it wants + // to actually remove the view. + mController.onChildToBeRemoved(view, index, () -> { + removeViewImmediateAndThen(view, callback); + mViewsToBeActuallyRemoved.remove(view); + }); + } + } else { + // Without a controller, nobody will animate this view out, so it gets an unceremonious + // departure. + removeViewImmediateAndThen(view, callback); + } + } + + /** Checks whether any animations of the given properties are still running. */ + public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) { + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : properties) { + if (getAnimationAtIndex(property, i).isRunning()) { + return true; + } + } + } + + return false; + } + + /** Cancels all animations that are running on all child views, for all properties. */ + public void cancelAllAnimations() { + if (mController == null) { + return; + } + + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + getAnimationAtIndex(property, i).cancel(); + } + } + } + + /** + * Animates the property of the child at the given index to the given value, then runs the + * callback provided when the animation ends. + */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel, + Runnable after) { + if (index < getChildCount()) { + final SpringAnimation animation = getAnimationAtIndex(property, index); + if (after != null) { + animation.addEndListener(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + after.run(); + } + }); + } + + if (startVel != Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animation.animateToFinalPosition(value); + } + } + + /** Shortcut to animate a value with a callback, but no start velocity. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + Runnable after) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after); + } + + /** Shortcut to animate a value with a start velocity, but no callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel) { + animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null); + } + + /** Shortcut to animate a value without changing the velocity or providing a callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null); + } + + /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */ + protected void animatePositionForChildAtIndex(int index, float x, float y) { + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x); + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y); + } + + /** Whether the first child would be left of center if translated to the given x value. */ + protected boolean isFirstChildXLeftOfCenter(float x) { + if (getChildCount() > 0) { + return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2; + } else { + return false; // If there's no first child, really anything is correct, right? + } + } + + /** ViewProperty's toString is useless, this returns a readable name for debug logging. */ + protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return "TRANSLATION_X"; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return "TRANSLATION_Y"; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return "SCALE_X"; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return "SCALE_Y"; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return "ALPHA"; + } else { + return "Unknown animation property."; + } + } + + + /** Immediately removes the view, without notifying the controller, then runs the callback. */ + private void removeViewImmediateAndThen(View view, Runnable callback) { + super.removeView(view); + + if (callback != null) { + callback.run(); + } + + setChildrenVisibility(); + } + + /** + * Retrieves the animation of the given property from the view at the given index via the view + * tag system. + */ + private SpringAnimation getAnimationAtIndex( + DynamicAnimation.ViewProperty property, int index) { + return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property)); + } + + /** Sets up SpringAnimations of the given property for each child view in the layout. */ + private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) { + for (int i = 0; i < getChildCount(); i++) { + setUpAnimationForChild(property, getChildAt(i), i); + } + } + + /** Constructs a SpringAnimation of the given property for a child view. */ + private void setUpAnimationForChild( + DynamicAnimation.ViewProperty property, View child, int index) { + SpringAnimation newAnim = new SpringAnimation(child, property); + newAnim.addUpdateListener((animation, value, velocity) -> { + final int nextAnimInChain = + mController.getNextAnimationInChain(property, indexOfChild(child)); + if (nextAnimInChain == PhysicsAnimationController.NONE) { + return; + } + + final int animIndex = indexOfChild(child); + final float offset = + mController.getOffsetForChainedPropertyAnimation(property); + + // If this property's animations should be chained, then check to see if there is a + // subsequent animation within the rendering limit, and if so, tell it to animate to + // this animation's new value (plus the offset). + if (nextAnimInChain < Math.min( + getChildCount(), + mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { + getAnimationAtIndex(property, animIndex + 1) + .animateToFinalPosition(value + offset); + } else if (nextAnimInChain < getChildCount()) { + // If the next child view is not rendered, update the property directly without + // animating it, so that the view is still in the correct state if it later + // becomes visible. + for (int i = nextAnimInChain; i < getChildCount(); i++) { + // 'value' here is the value of the last child within the rendering limit, + // not the first child's value - so we want to subtract the last child's + // index when calculating the offset. + property.setValue(getChildAt(i), value + offset * (i - animIndex)); + } + } + }); + + newAnim.setSpring(mController.getSpringForce(property, child)); + newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property)); + child.setTag(getTagIdForProperty(property), newAnim); + } + + /** Hides children beyond the max rendering count. */ + private void setChildrenVisibility() { + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).setVisibility( + // Ignore views that are animating out when calculating whether to hide the + // view. That is, if we're supposed to render 5 views, but 4 are animating out + // and will soon be removed, render up to 9 views temporarily. + i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) + ? View.VISIBLE + : View.GONE); + } + } + + /** Return a stable ID to use as a tag key for the given property's animations. */ + private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return R.id.translation_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return R.id.translation_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return R.id.scale_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return R.id.scale_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return R.id.alpha_dynamicanimation_tag; + } + + return -1; + } + + /** + * End listener that is added to each individual DynamicAnimation, which dispatches to a single + * listener when every other animation of the given property is no longer running. + * + * This is required since chained DynamicAnimations can stop and start again due to changes in + * upstream animations. This means that adding an end listener to just the last animation is not + * sufficient. By firing only when every other animation on the property has stopped running, we + * ensure that no animation will be restarted after the single end listener is called. + */ + protected class AllAnimationsForPropertyFinishedEndListener + implements DynamicAnimation.OnAnimationEndListener { + private DynamicAnimation.ViewProperty mProperty; + + AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) { + this.mProperty = property; + } + + @Override + public void onAnimationEnd( + DynamicAnimation anim, boolean canceled, float value, float velocity) { + if (!arePropertiesAnimating(mProperty)) { + if (mEndListenerForProperty.containsKey(mProperty)) { + mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value, + velocity); + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java new file mode 100644 index 000000000000..0f5137618258 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.HashMap; +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop + * each other with a slight offset to the left or right (depending on which side of the screen they + * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of + * the screen. + */ +public class StackAnimationController extends + PhysicsAnimationLayout.PhysicsAnimationController { + + private static final String TAG = "Bubbs.StackCtrl"; + + /** Scale factor to use initially for new bubbles being animated in. */ + private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; + + /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ + private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; + + /** + * Values to use for the default {@link SpringForce} provided to the physics animation layout. + */ + private static final float DEFAULT_STIFFNESS = 2500f; + private static final float DEFAULT_BOUNCINESS = 0.85f; + + /** + * The canonical position of the stack. This is typically the position of the first bubble, but + * we need to keep track of it separately from the first bubble's translation in case there are + * no bubbles, or the first bubble was just added and being animated to its new position. + */ + private PointF mStackPosition = new PointF(); + + /** + * Animations on the stack position itself, which would have been started in + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to + * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect) + * to a legal position on the side of the screen. + */ + private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations = + new HashMap<>(); + + /** Horizontal offset of bubbles in the stack. */ + private float mStackOffset; + /** Diameter of the bubbles themselves. */ + private int mIndividualBubbleSize; + /** Size of spacing around the bubbles, separating it from the edge of the screen. */ + private int mBubblePadding; + /** How far offscreen the stack rests. */ + private int mBubbleOffscreen; + /** How far down the screen the stack starts, when there is no pre-existing location. */ + private int mStackStartingVerticalOffset; + + private Point mDisplaySize; + private RectF mAllowableStackPositionRegion; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + + mDisplaySize = new Point(); + WindowManager wm = + (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getSize(mDisplaySize); + } + + /** + * Instantly move the first bubble to the given point, and animate the rest of the stack behind + * it with the 'following' effect. + */ + public void moveFirstBubbleWithStackFollowing(float x, float y) { + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x); + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y); + } + + /** + * The position of the stack - typically the position of the first bubble; if no bubbles have + * been added yet, it will be where the first bubble will go when added. + */ + public PointF getStackPosition() { + return mStackPosition; + } + + /** + * Flings the first bubble along the given property's axis, using the provided configuration + * values. When the animation ends - either by hitting the min/max, or by friction sufficiently + * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final + * position. + */ + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, + float vel, + float friction, + SpringForce spring, + Float finalPosition) { + Log.d(TAG, String.format("Flinging %s.", + PhysicsAnimationLayout.getReadablePropertyName(property))); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + final float currentValue = firstBubbleProperty.getValue(this); + final RectF bounds = getAllowableStackPositionRegion(); + final float min = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.left + : bounds.top; + final float max = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.right + : bounds.bottom; + + FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty); + flingAnimation.setFriction(friction) + .setStartVelocity(vel) + + // If the bubble's property value starts beyond the desired min/max, use that value + // instead so that the animation won't immediately end. If, for example, the user + // drags the bubbles into the navigation bar, but then flings them upward, we want + // the fling to occur despite temporarily having a value outside of the min/max. If + // the bubbles are out of bounds and flung even farther out of bounds, the fling + // animation will halt immediately and the SpringAnimation will take over, springing + // it in reverse to the (legal) final position. + .setMinValue(Math.min(currentValue, min)) + .setMaxValue(Math.max(currentValue, max)) + + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (!canceled) { + springFirstBubbleWithStackFollowing(property, spring, endVelocity, + finalPosition != null + ? finalPosition + : Math.max(min, Math.min(max, endValue))); + } + }); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, flingAnimation); + flingAnimation.start(); + } + + /** + * Cancel any stack position animations that were started by calling + * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end + * listeners. + */ + public void cancelStackPositionAnimations() { + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X); + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y); + + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y); + } + + /** + * Returns the region within which the stack is allowed to rest. This goes slightly off the left + * and right sides of the screen, below the status bar/cutout and above the navigation bar. + * While the stack is not allowed to rest outside of these bounds, it can temporarily be + * animated or dragged beyond them. + */ + public RectF getAllowableStackPositionRegion() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + mAllowableStackPositionRegion = new RectF(); + + if (insets != null) { + mAllowableStackPositionRegion.left = + -mBubbleOffscreen + - mBubblePadding + + Math.max( + insets.getSystemWindowInsetLeft(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetLeft() + : 0); + mAllowableStackPositionRegion.right = + mLayout.getWidth() + - mIndividualBubbleSize + + mBubbleOffscreen + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetRight(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetRight() + : 0); + + mAllowableStackPositionRegion.top = + mBubblePadding + + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); + mAllowableStackPositionRegion.bottom = + mLayout.getHeight() + - mIndividualBubbleSize + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetBottom(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() + : 0); + } + + return mAllowableStackPositionRegion; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, // For positioning. + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.ALPHA, // For fading in new bubbles. + DynamicAnimation.SCALE_X, // For 'popping in' new bubbles. + DynamicAnimation.SCALE_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + if (property.equals(DynamicAnimation.TRANSLATION_X) + || property.equals(DynamicAnimation.TRANSLATION_Y)) { + return index + 1; // Just chain them linearly. + } else { + return NONE; + } + } + + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + // Offset to the left if we're on the left, or the right otherwise. + return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x) + ? -mStackOffset : mStackOffset; + } else { + return 0f; + } + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setDampingRatio(DEFAULT_BOUNCINESS) + .setStiffness(DEFAULT_STIFFNESS); + } + + @Override + void onChildAdded(View child, int index) { + // If this is the first child added, position the stack in its starting position. + if (mLayout.getChildCount() == 1) { + moveStackToStartPosition(); + } + + if (mLayout.indexOfChild(child) == 0) { + child.setTranslationY(mStackPosition.y); + + // Pop in the new bubble. + child.setScaleX(ANIMATE_IN_STARTING_SCALE); + child.setScaleY(ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f); + + // Fade in the new bubble. + child.setAlpha(0); + mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f); + + // Start the new bubble 4x the normal offset distance in the opposite direction. We'll + // animate in from this position. Since the animations are chained, when the new bubble + // flies in from the side, it will push the other ones out of the way. + float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); + } + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // Animate the child out, actually removing it once its alpha is zero. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.ALPHA, index, 0f, () -> { + actuallyRemove.run(); + }); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); + + final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); + if (hasPrecedingChild) { + final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); + if (precedingViewIndex >= 0) { + final float offsetX = + getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + mLayout.animatePositionForChildAtIndex( + precedingViewIndex, + mStackPosition.x + (index * offsetX), + mStackPosition.y); + } + } + } + + /** Moves the stack, without any animation, to the starting position. */ + private void moveStackToStartPosition() { + mLayout.post(() -> setStackPosition( + getAllowableStackPositionRegion().right, + getAllowableStackPositionRegion().top + mStackStartingVerticalOffset)); + } + + /** + * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent + * bubbles to animate 'following' to the new location. + */ + private void moveFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float value) { + + // Update the canonical stack position. + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + mStackPosition.x = value; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + mStackPosition.y = value; + } + + if (mLayout.getChildCount() > 0) { + property.setValue(mLayout.getChildAt(0), value); + mLayout.animateValueForChildAtIndex( + property, + /* index */ 1, + value + getOffsetForChainedPropertyAnimation(property)); + } + } + + /** Moves the stack to a position instantly, with no animation. */ + private void setStackPosition(float x, float y) { + Log.d(TAG, String.format("Setting position to (%f, %f).", x, y)); + mStackPosition.set(x, y); + + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(y + (i * yOffset)); + } + } + + /** + * Springs the first bubble to the given final position, with the rest of the stack 'following'. + */ + private void springFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, SpringForce spring, + float vel, float finalPosition) { + + Log.d(TAG, String.format("Springing %s to final position %f.", + PhysicsAnimationLayout.getReadablePropertyName(property), + finalPosition)); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + SpringAnimation springAnimation = + new SpringAnimation(this, firstBubbleProperty) + .setSpring(spring) + .setStartVelocity(vel); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, springAnimation); + springAnimation.animateToFinalPosition(finalPosition); + } + + /** + * Cancels any outstanding first bubble property animations that are running. This does not + * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only + * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. + */ + private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) { + if (mStackPositionAnimations.containsKey(property)) { + mStackPositionAnimations.get(property).cancel(); + } + } + + /** + * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's + * translation and animate the rest of the stack with it. A DynamicAnimation can animate this + * property directly to move the first bubble and cause the stack to 'follow' to the new + * location. + * + * This could also be achieved by simply animating the first bubble view and adding an update + * listener to dispatch movement to the rest of the stack. However, this would require + * duplication of logic in that update handler - it's simpler to keep all logic contained in the + * {@link #moveFirstBubbleWithStackFollowing} method. + */ + private class StackPositionProperty + extends FloatPropertyCompat<StackAnimationController> { + private final DynamicAnimation.ViewProperty mProperty; + + private StackPositionProperty(DynamicAnimation.ViewProperty property) { + super(property.toString()); + mProperty = property; + } + + @Override + public float getValue(StackAnimationController controller) { + return mProperty.getValue(mLayout.getChildAt(0)); + } + + @Override + public void setValue(StackAnimationController controller, float value) { + moveFirstBubbleWithStackFollowing(mProperty, value); + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 7b18fad0e105..f5ac0d39d61f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1090,9 +1090,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + protected int getActionLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid_item; + } + return com.android.systemui.R.layout.global_actions_item; + } + public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent, + View v = inflater.inflate(getActionLayoutId(context), parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); @@ -1498,7 +1505,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.setBackgroundDrawable(mGradientDrawable); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - setContentView(com.android.systemui.R.layout.global_actions_wrapped); + + setContentView(getGlobalActionsLayoutId(context)); mGlobalActionsLayout = (MultiListLayout) findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); @@ -1515,6 +1523,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, setTitle(R.string.global_actions); } + private int getGlobalActionsLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid; + } + return com.android.systemui.R.layout.global_actions_wrapped; + } + private void updateList() { mGlobalActionsLayout.removeAllItems(); ArrayList<Action> separatedActions = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java new file mode 100644 index 000000000000..0e49b5f3cd2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.MultiListLayout; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsGridLayout extends MultiListLayout { + + boolean mBackgroundsSet; + + public GlobalActionsGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void setBackgrounds() { + HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); + HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true, + getContext()); + getListView().setBackground(listBackground); + getSeparatedView().setBackground(separatedViewBackground); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + if (getListView() != null && !mBackgroundsSet) { + setBackgrounds(); + mBackgroundsSet = true; + } + } + + @Override + public void setExpectedListItemCount(int count) { + mExpectedListItemCount = count; + getListView().setExpectedCount(count); + } + + @Override + protected ViewGroup getSeparatedView() { + return findViewById(com.android.systemui.R.id.separated_button); + } + + @Override + protected ListGridLayout getListView() { + return findViewById(android.R.id.list); + } + + @Override + public void removeAllItems() { + ViewGroup separatedList = getSeparatedView(); + ListGridLayout list = getListView(); + if (separatedList != null) { + separatedList.removeAllViews(); + } + if (list != null) { + list.removeAllItems(); + } + } + + @Override + public ViewGroup getParentView(boolean separated, int index) { + if (separated) { + return getSeparatedView(); + } else { + return getListView().getParentView(index); + } + } + + /** + * Not used in this implementation of the Global Actions Menu, but necessary for some others. + */ + @Override + public void setDivisionView(View v) { + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java new file mode 100644 index 000000000000..37755155751f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * Layout which uses nested LinearLayouts to create a grid with the following behavior: + * + * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item + * count. + * * Display and hide sub-lists as needed, depending on the expected item count. + * * Favor bias toward having more rows or columns depending on the orientation of the device + * (TODO(123344999): Implement this, currently always favors adding more rows.) + * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows + * or columns depending on the orientation of the device. + * (TODO(123344999): Implement this, currently always columns.) + * + * While we could implement this behavior with a GridLayout, it would take significantly more + * time and effort, and would require more substantial refactoring of the existing code in + * GlobalActionsDialog, since it would require manipulation of the child items themselves. + * + */ + +public class ListGridLayout extends LinearLayout { + private int mExpectedCount; + private int mRows; + private int mColumns; + + public ListGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Remove all items from this grid. + */ + public void removeAllItems() { + for (int i = 0; i < getChildCount(); i++) { + ViewGroup subList = (ViewGroup) getChildAt(i); + if (subList != null) { + subList.removeAllViews(); + } + } + } + + /** + * Get the parent view associated with the item which should be placed at the given position. + */ + public ViewGroup getParentView(int index) { + ViewGroup firstParent = (ViewGroup) getChildAt(0); + if (mRows == 0) { + return firstParent; + } + int column = (int) Math.floor(index / mRows); + ViewGroup parent = (ViewGroup) getChildAt(column); + return parent != null ? parent : firstParent; + } + + /** + * Sets the expected number of items that this grid will be responsible for rendering. + */ + public void setExpectedCount(int count) { + mExpectedCount = count; + mRows = getRowCount(); + mColumns = getColumnCount(); + + for (int i = 0; i < getChildCount(); i++) { + if (i <= mColumns) { + setSublistVisibility(i, true); + } else { + setSublistVisibility(i, false); + } + } + + } + + private void setSublistVisibility(int index, boolean visible) { + View subList = getChildAt(index); + Log.d("ListGrid", "index: " + index + ", visibility: " + visible); + if (subList != null) { + subList.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + private int getRowCount() { + return (int) Math.ceil(Math.sqrt(mExpectedCount)); + } + + private int getColumnCount() { + return (int) Math.round(Math.sqrt(mExpectedCount)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/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/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index d740033c9f48..a0f4e24d2f93 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -24,7 +24,6 @@ import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; -import com.android.systemui.R.drawable; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.QSHost; @@ -38,7 +37,7 @@ import javax.inject.Inject; /** Quick settings tile: Location **/ public class LocationTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(drawable.ic_signal_location); + private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_signal_location); private final LocationController mController; private final KeyguardMonitor mKeyguard; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 43ce0b50e03d..de78d3376500 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -111,7 +111,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> protected void handleUpdateState(BooleanState state, Object arg) { state.value = mController.isActivated(); state.label = mContext.getString(R.string.quick_settings_night_display_label); - state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on); + state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.secondaryLabel = getSecondaryLabel(state.value); 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/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 60a20cf15cc0..44ff4a73a4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarWindowController; @@ -61,7 +62,6 @@ public class BubbleControllerTest extends SysuiTestCase { private IActivityManager mActivityManager; @Mock private DozeParameters mDozeParameters; - @Mock private FrameLayout mStatusBarView; @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @@ -80,6 +80,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mStatusBarView = new FrameLayout(mContext); mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); // Bubbles get added to status bar window view @@ -189,5 +190,10 @@ public class BubbleControllerTest extends SysuiTestCase { StatusBarWindowController statusBarWindowController) { super(context, statusBarWindowController); } + + @Override + public boolean shouldAutoBubble(Context c, NotificationEntry entry) { + return entry.notification.getNotification().getBubbleMetadata() != null; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java new file mode 100644 index 000000000000..1bb7ef4a657b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Resources; +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private ExpandedAnimationController mExpandedController = new ExpandedAnimationController(); + + private int mStackOffset; + private float mBubblePadding; + private float mBubbleSize; + + private PointF mExpansionPoint; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mExpandedController); + Resources res = mLayout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + } + + @Test + public void testExpansionAndCollapse() throws InterruptedException { + mExpansionPoint = new PointF(100, 100); + Runnable afterExpand = Mockito.mock(Runnable.class); + mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testExpanded(); + Mockito.verify(afterExpand).run(); + + Runnable afterCollapse = Mockito.mock(Runnable.class); + mExpandedController.collapseBackToStack(afterCollapse); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); + Mockito.verify(afterExpand).run(); + } + + /** Check that children are in the correct positions for being stacked. */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** Check that children are in the correct positions for being expanded. */ + private void testExpanded() { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)), + mViews.get(i).getTranslationX(), + 2f); + assertEquals(mBubblePadding + mCutoutInsetSize, + mViews.get(i).getTranslationY(), 2f); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java new file mode 100644 index 000000000000..bfc02d902416 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; + +import android.os.SystemClock; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */ +public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { + static final float TEST_TRANSLATION_X_OFFSET = 15f; + + @Spy + private TestableAnimationController mTestableController = new TestableAnimationController(); + + @Before + public void setUp() throws Exception { + super.setUp(); + + // By default, use translation animations, chain the X animations with the default + // offset, and don't actually remove views immediately (since most implementations will wait + // to animate child views out before actually removing them). + mTestableController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X)); + mTestableController.setOffsetForProperty( + DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET); + mTestableController.setRemoveImmediately(false); + } + + @Test + public void testRenderVisibility() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // The last child should be GONE, the rest VISIBLE. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, + mLayout.getChildAt(i).getVisibility()); + } + } + + @Test + public void testHierarchyChanges() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Make sure the controller was notified of all the views we added. + for (View mView : mViews) { + Mockito.verify(mTestableController).onChildAdded(mView, 0); + } + + // Remove some views and ensure the controller was notified, with the proper indices. + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + mLayout.removeView(mViews.get(2)); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(1)), eq(1), any()); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(2)), eq(1), any()); + + // Make sure we still get view added notifications after doing some removals. + final View newBubble = new FrameLayout(mContext); + mLayout.addView(newBubble, 0); + Mockito.verify(mTestableController).onChildAdded(newBubble, 0); + } + + @Test + public void testUpdateValueNotChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Don't chain any values. + mTestableController.setChainedProperties(Sets.newHashSet()); + + // Child views should not be translated. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + // Animate the first child's translation X. + final CountDownLatch animLatch = new CountDownLatch(1); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100, + animLatch::countDown); + animLatch.await(1, TimeUnit.SECONDS); + + // Ensure that the first view has been translated, but not the second one. + assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + } + + @Test + public void testUpdateValueXChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + testChainedTranslationAnimations(); + } + + @Test + public void testSetEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + final CountDownLatch yLatch = new CountDownLatch(1); + final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + yLatch.countDown(); + } + }); + + // Set end listeners for both x and y. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + yLatch.await(1, TimeUnit.SECONDS); + + // Make sure the x end listener was called only one time, and the y listener was never + // called since we didn't animate y. Wait 1 second after the original animation end trigger + // to make sure it doesn't get called again. + Mockito.verify(xEndListener, Mockito.after(1000).times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + Mockito.verify(yEndListener, Mockito.after(1000).never()) + .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat()); + } + + @Test + public void testRemoveEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + // Set the end listener. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + + InOrder endListenerCalls = inOrder(xEndListener); + endListenerCalls.verify(xEndListener, Mockito.times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + + // Animate X again, remove the end listener. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + xLatch.await(1, TimeUnit.SECONDS); + + // Make sure the end listener was not called. + endListenerCalls.verifyNoMoreInteractions(); + } + + @Test + public void testPrecedingNonRemovedIndex() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Call removeView at index 4, but don't actually remove it yet (as if we're animating it + // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since + // 4 is on its way out. + assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); + mLayout.removeView(mViews.get(4)); + assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); + + // Call removeView at index 1, and actually remove it immediately. With the old view at 1 + // instantly gone, the preceding view to 0 should be 1 in both cases. + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + } + + @Test + public void testSetController() throws InterruptedException { + // Add the bubbles, then set the controller, to make sure that a controller added to an + // already-initialized view works correctly. + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mTestableController); + testChainedTranslationAnimations(); + + TestableAnimationController secondController = + Mockito.spy(new TestableAnimationController()); + secondController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y)); + secondController.setChainedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X)); + secondController.setOffsetForProperty( + DynamicAnimation.SCALE_X, 10f); + secondController.setRemoveImmediately(true); + + mLayout.setController(secondController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, + 0, + 1.5f); + + waitForPropertyAnimations(DynamicAnimation.SCALE_X); + + // Make sure we never asked the original controller about any SCALE animations, that would + // mean the controller wasn't switched over properly. + Mockito.verify(mTestableController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(mTestableController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + // Make sure we asked the new controller about its animated properties, and configuration + // options. + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getAnimatedProperties(); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + mLayout.setController(mTestableController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Make sure we never asked the second controller about the TRANSLATION_X animation. + Mockito.verify(secondController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt()); + Mockito.verify(secondController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X)); + + } + + @Test + public void testArePropertiesAnimating() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + // Wait for the animations to get underway. + SystemClock.sleep(50); + + assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X)); + assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y)); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + } + + @Test + public void testCancelAllAnimations() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 1000); + + mLayout.cancelAllAnimations(); + + waitForLayoutMessageQueue(); + + // Animations should be somewhere before their end point. + assertTrue(mViews.get(0).getTranslationX() < 1000); + assertTrue(mViews.get(0).getTranslationY() < 1000); + } + + + /** Standard test of chained translation animations. */ + private void testChainedTranslationAnimations() throws InterruptedException { + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Since we enabled chaining, animating the first view to 100 should animate the second to + // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble + // not being visible, or animated, make sure that it has the appropriate chained + // translation. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals( + 100 + i * TEST_TRANSLATION_X_OFFSET, + mLayout.getChildAt(i).getTranslationX(), .1f); + } + + // Ensure that the Y translations were unaffected. + assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + + // Animate the first child's Y translation. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y); + + // Ensure that only the first view's Y translation chained, since we only chained X + // translations. + assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + } + + /** + * Animation controller with configuration methods whose return values can be set by individual + * tests. + */ + private class TestableAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>(); + private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>(); + private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>(); + private boolean mRemoveImmediately = false; + + void setAnimatedProperties( + Set<DynamicAnimation.ViewProperty> animatedProperties) { + mAnimatedProperties = animatedProperties; + } + + void setChainedProperties( + Set<DynamicAnimation.ViewProperty> chainedProperties) { + mChainedProperties = chainedProperties; + } + + void setOffsetForProperty( + DynamicAnimation.ViewProperty property, float offset) { + mOffsetForProperty.put(property, offset); + } + + public void setRemoveImmediately(boolean removeImmediately) { + mRemoveImmediately = removeImmediately; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return mAnimatedProperties; + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return mChainedProperties.contains(property) ? index + 1 : NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return mOffsetForProperty.getOrDefault(property, 0f); + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce(); + } + + @Override + void onChildAdded(View child, int index) {} + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + if (mRemoveImmediately) { + actuallyRemove.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java new file mode 100644 index 000000000000..186a76219536 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.DisplayCutout; +import android.view.View; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a + * testable version of the layout, and provides some helpful methods to add views to the layout and + * wait for physics animations to finish running. + * + * See physics-animation-testing.md. + */ +public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { + TestablePhysicsAnimationLayout mLayout; + List<View> mViews = new ArrayList<>(); + + Handler mMainThreadHandler; + + int mMaxRenderedBubbles; + int mSystemWindowInsetSize = 50; + int mCutoutInsetSize = 100; + + int mWidth = 1000; + int mHeight = 1000; + + @Mock + private WindowInsets mWindowInsets; + + @Mock + private DisplayCutout mCutout; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mLayout = new TestablePhysicsAnimationLayout(mContext); + mLayout.setLeft(0); + mLayout.setRight(mWidth); + mLayout.setTop(0); + mLayout.setBottom(mHeight); + + mMaxRenderedBubbles = + getContext().getResources().getInteger(R.integer.bubbles_max_rendered); + mMainThreadHandler = new Handler(Looper.getMainLooper()); + + when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize); + + when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout); + when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize); + } + + /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ + void addOneMoreThanRenderLimitBubbles() { + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + final View newView = new FrameLayout(mContext); + mLayout.addView(newView, 0); + mViews.add(0, newView); + + newView.setTranslationX(0); + newView.setTranslationY(0); + } + } + + /** + * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties' + * animations to finish before allowing the test to proceed. + */ + void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties) + throws InterruptedException { + final CountDownLatch animLatch = new CountDownLatch(properties.length); + for (DynamicAnimation.ViewProperty property : properties) { + mLayout.setTestEndListenerForProperty(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + animLatch.countDown(); + } + }, property); + } + animLatch.await(1, TimeUnit.SECONDS); + } + + /** Uses a latch to wait for the message queue to finish. */ + void waitForLayoutMessageQueue() throws InterruptedException { + // Wait for layout, then the view should be actually removed. + CountDownLatch layoutLatch = new CountDownLatch(1); + mLayout.post(layoutLatch::countDown); + layoutLatch.await(1, TimeUnit.SECONDS); + } + + /** + * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations + * are run on the main thread, which is a requirement of DynamicAnimation. + */ + protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout { + public TestablePhysicsAnimationLayout(Context context) { + super(context); + } + + @Override + public void setController(PhysicsAnimationController controller) { + mMainThreadHandler.post(() -> super.setController(controller)); + try { + waitForLayoutMessageQueue(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void cancelAllAnimations() { + mMainThreadHandler.post(super::cancelAllAnimations); + } + + @Override + protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property, + int index, float value, float startVel, Runnable after) { + mMainThreadHandler.post(() -> + super.animateValueForChildAtIndex(property, index, value, startVel, after)); + } + + @Override + public WindowInsets getRootWindowInsets() { + return mWindowInsets; + } + + /** + * Sets an end listener that will be called after the 'real' end listener that was already + * set. + */ + private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + final DynamicAnimation.OnAnimationEndListener realEndListener = + mEndListenerForProperty.get(property); + + setEndListenerForProperty((animation, canceled, value, velocity) -> { + if (realEndListener != null) { + realEndListener.onAnimationEnd(animation, canceled, value, velocity); + } + + listener.onAnimationEnd(animation, canceled, value, velocity); + }, property); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java new file mode 100644 index 000000000000..db819d57417b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private TestableStackController mStackController = new TestableStackController(); + + private int mStackOffset; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mStackController); + mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); + } + + /** + * Test moving around the stack, and make sure the position is updated correctly, and the stack + * direction is correct. + */ + @Test + public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { + mStackController.moveFirstBubbleWithStackFollowing(200, 100); + + // The first bubble should have moved instantly, the rest should be waiting for animation. + assertEquals(200, mViews.get(0).getTranslationX(), .1f); + assertEquals(100, mViews.get(0).getTranslationY(), .1f); + assertEquals(0, mViews.get(1).getTranslationX(), .1f); + assertEquals(0, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack got moved to the right place and is stacked to the left. + testStackedAtPosition(200, 100, -1); + assertEquals(new PointF(200, 100), mStackController.getStackPosition()); + + mStackController.moveFirstBubbleWithStackFollowing(1000, 500); + + // The first bubble again should have moved instantly while the rest remained where they + // were until the animation takes over. + assertEquals(1000, mViews.get(0).getTranslationX(), .1f); + assertEquals(500, mViews.get(0).getTranslationY(), .1f); + assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f); + assertEquals(100, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + testStackedAtPosition(1000, 500, 1); + assertEquals(new PointF(1000, 500), mStackController.getStackPosition()); + } + + @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") + public void testFlingSideways() throws InterruptedException { + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 5000f, 1.15f, new SpringForce(), mWidth * 1f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 0f, 1.15f, new SpringForce(), 0f); + + // Nothing should move initially since the animations haven't begun, including the first + // view. + assertEquals(0f, mViews.get(0).getTranslationX(), 1f); + assertEquals(0f, mViews.get(0).getTranslationY(), 1f); + + // Wait for the flinging. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Wait for the springing. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung all the way to the right side, with the + // stack stacked off to the right now. + testStackedAtPosition(mWidth * 1f, 0f, 1); + } + + @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") + public void testFlingUpFromBelowBottomCenter() throws InterruptedException { + // Move to the center of the screen, just past the bottom. + mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 0, 1.15f, new SpringForce(), 27f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 5000f, 1.15f, new SpringForce(), 27f); + + // Nothing should move initially since the animations haven't begun. + assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f); + assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung a bit but then sprung to the final + // destination which is (27, 27). + testStackedAtPosition(27, 27, -1); + } + + @Test + public void testChildAdded() throws InterruptedException { + // Move the stack to y = 500. + mStackController.moveFirstBubbleWithStackFollowing(0f, 500f); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + final View newView = new FrameLayout(mContext); + mLayout.addView( + newView, + 0, + new FrameLayout.LayoutParams(50, 50)); + + waitForPropertyAnimations( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.SCALE_X, + DynamicAnimation.SCALE_Y); + + // The new view should be at the top of the stack, in the correct position. + assertEquals(0f, newView.getTranslationX(), .1f); + assertEquals(500f, newView.getTranslationY(), .1f); + assertEquals(1f, newView.getScaleX(), .1f); + assertEquals(1f, newView.getScaleY(), .1f); + assertEquals(1f, newView.getAlpha(), .1f); + } + + @Test + public void testChildRemoved() throws InterruptedException { + final View firstView = mLayout.getChildAt(0); + mLayout.removeView(firstView); + + // The view should still be there, since the controller is animating it out and hasn't yet + // actually removed it from the parent view. + assertEquals(0, mLayout.indexOfChild(firstView)); + + waitForPropertyAnimations(DynamicAnimation.ALPHA); + waitForLayoutMessageQueue(); + + assertEquals(-1, mLayout.indexOfChild(firstView)); + + // The subsequent view should have been translated over to 0, not stacked off to the left. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + } + + /** + * Checks every child view to make sure it's stacked at the given coordinates, off to the left + * or right side depending on offset multiplier. + */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** + * Testable version of the stack controller that dispatches its animations on the main thread. + */ + private class TestableStackController extends StackAnimationController { + @Override + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float vel, float friction, + SpringForce spring, Float finalPosition) { + mMainThreadHandler.post(() -> + super.flingThenSpringFirstBubbleWithStackFollowing( + property, vel, friction, spring, finalPosition)); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a96676e5fc5f..5d6c2f074f9d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -677,6 +677,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -1649,6 +1650,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName); intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback); 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 353749f211c1..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; @@ -776,6 +819,7 @@ public final class BroadcastQueue { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -860,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(); @@ -936,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 @@ -953,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 @@ -966,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 @@ -992,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); + } } } @@ -1030,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... @@ -1065,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); @@ -1473,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 @@ -1487,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 @@ -1502,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; } @@ -1618,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; } @@ -1664,7 +1796,7 @@ public final class BroadcastQueue { } final boolean isIdle() { - return mParallelBroadcasts.isEmpty() && mOrderedBroadcasts.isEmpty() + return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty() && (mPendingBroadcast == null); } @@ -1676,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); } @@ -1720,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--) { @@ -1739,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); @@ -1772,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 3b4e4dd02edd..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; @@ -1405,7 +1407,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( - mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady); + mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady); updateCurrentProfileIds(); AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); @@ -1694,7 +1696,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList); final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodListLocked(); } @@ -1750,7 +1752,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return Collections.emptyList(); } final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); } @@ -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 88d1a9c0eb6c..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; @@ -66,9 +67,9 @@ import java.util.Locale; */ final class InputMethodUtils { public static final boolean DEBUG = false; - public static final int NOT_A_SUBTYPE_ID = -1; - public static final String SUBTYPE_MODE_ANY = null; - public static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + static final int NOT_A_SUBTYPE_ID = -1; + private static final String SUBTYPE_MODE_ANY = null; + static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String TAG = "InputMethodUtils"; private static final Locale ENGLISH_LOCALE = new Locale("en"); private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); @@ -108,7 +109,7 @@ final class InputMethodUtils { // ---------------------------------------------------------------------- // Utilities for debug - public static String getApiCallStack() { + static String getApiCallStack() { String apiCallStack = ""; try { throw new RuntimeException(); @@ -131,10 +132,9 @@ final class InputMethodUtils { } // ---------------------------------------------------------------------- - public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale requiredLocale, final boolean checkCountry, - final String requiredSubtypeMode) { + private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, + boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, + String requiredSubtypeMode) { if (!imi.isSystem()) { return false; } @@ -148,8 +148,8 @@ final class InputMethodUtils { } @Nullable - public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, - final Context context) { + private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, + Context context) { // At first, find the fallback locale from the IMEs that are declared as "default" in the // current locale. Note that IME developers can declare an IME as "default" only for // some particular locales but "not default" for other locales. @@ -177,8 +177,8 @@ final class InputMethodUtils { return null; } - private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute) { + private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, + Context context, boolean checkDefaultAttribute) { if (!imi.isSystem()) { return false; } @@ -198,7 +198,7 @@ final class InputMethodUtils { return false; } - public static Locale getSystemLocaleFromContext(final Context context) { + private static Locale getSystemLocaleFromContext(Context context) { try { return context.getResources().getConfiguration().locale; } catch (Resources.NotFoundException ex) { @@ -212,10 +212,9 @@ final class InputMethodUtils { @NonNull private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); - public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale locale, final boolean checkCountry, - final String requiredSubtypeMode) { + InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context, + boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, + String requiredSubtypeMode) { for (int i = 0; i < imis.size(); ++i) { final InputMethodInfo imi = imis.get(i); if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale, @@ -228,8 +227,7 @@ final class InputMethodUtils { // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be // documented more clearly. - public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis, - final Context context) { + InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { // If one or more auxiliary input methods are available, OK to stop populating the list. for (final InputMethodInfo imi : mInputMethodSet) { if (imi.isAuxiliaryIme()) { @@ -269,8 +267,8 @@ final class InputMethodUtils { } private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( - final ArrayList<InputMethodInfo> imis, final Context context, - @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) { + ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, + @Nullable Locale fallbackLocale) { // Once the system becomes ready, we pick up at least one keyboard in the following order. // Secondary users fall into this category in general. // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true @@ -317,7 +315,7 @@ final class InputMethodUtils { return builder; } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) { final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); // We will primarily rely on the system locale, but also keep relying on the fallback locale @@ -336,13 +334,13 @@ final class InputMethodUtils { return builder.build(); } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis) { return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); } - public static boolean containsSubtypeOf(final InputMethodInfo imi, - @Nullable final Locale locale, final boolean checkCountry, final String mode) { + static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, + boolean checkCountry, String mode) { if (locale == null) { return false; } @@ -371,7 +369,7 @@ final class InputMethodUtils { return false; } - public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -380,7 +378,7 @@ final class InputMethodUtils { return subtypes; } - public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { + static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { if (enabledImes == null || enabledImes.isEmpty()) { return null; } @@ -404,11 +402,11 @@ final class InputMethodUtils { return enabledImes.get(Math.max(firstFoundSystemIme, 0)); } - public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { + static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; } - public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { if (imi != null) { final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -430,7 +428,7 @@ final class InputMethodUtils { }; @VisibleForTesting - public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( + static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi) { final LocaleList systemLocales = res.getConfiguration().getLocales(); @@ -564,7 +562,7 @@ final class InputMethodUtils { * it will return the first subtype matched with mode * @return the most applicable subtypeId */ - public static InputMethodSubtype findLastResortApplicableSubtypeLocked( + static InputMethodSubtype findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort) { if (subtypes == null || subtypes.size() == 0) { @@ -615,14 +613,13 @@ final class InputMethodUtils { return applicableSubtype; } - public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { if (subtype == null) return true; return !subtype.isAuxiliary(); } - public static void setNonSelectedSystemImesDisabledUntilUsed( - IPackageManager packageManager, List<InputMethodInfo> enabledImis, - int userId, String callingPackage) { + static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, + List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) { if (DEBUG) { Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); } @@ -710,7 +707,7 @@ final class InputMethodUtils { } } - public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, + static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype) { final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); return subtype != null @@ -730,8 +727,8 @@ final class InputMethodUtils { * @param packageName the package name. * @return {@code true} if the package name belongs to the UID. */ - public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager, - final int uid, final String packageName) { + static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager, + @UserIdInt int uid, String packageName) { try { appOpsManager.checkPackage(uid, packageName); return true; @@ -760,6 +757,14 @@ final class InputMethodUtils { */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); + private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { + Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); + } + + private static final UserManagerInternal sUserManagerInternal = + LocalServices.getService(UserManagerInternal.class); + private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; @@ -802,10 +807,9 @@ final class InputMethodUtils { return imsList; } - public InputMethodSettings( - Resources res, ContentResolver resolver, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, - @UserIdInt int userId, boolean copyOnWrite) { + InputMethodSettings(Resources res, ContentResolver resolver, + ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, + boolean copyOnWrite) { mRes = res; mResolver = resolver; mMethodMap = methodMap; @@ -820,7 +824,7 @@ final class InputMethodUtils { * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual * settings on the {@link Settings.Secure} until we do the first write operation. */ - public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { + void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { if (DEBUG) { Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); } @@ -834,16 +838,18 @@ final class InputMethodUtils { // TODO: mCurrentProfileIds should be updated here. } - private void putString(@NonNull final String key, @Nullable final String str) { + private void putString(@NonNull String key, @Nullable String str) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putStringForUser(mResolver, key, str, userId); } } @Nullable - private String getString(@NonNull final String key, @Nullable final String defaultValue) { + private String getString(@NonNull String key, @Nullable String defaultValue) { final String result; if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { result = mCopyOnWriteDataStore.get(key); @@ -853,27 +859,29 @@ final class InputMethodUtils { return result != null ? result : defaultValue; } - private void putInt(final String key, final int value) { + private void putInt(String key, int value) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putIntForUser(mResolver, key, value, userId); } } - private int getInt(final String key, final int defaultValue) { + private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); - return result != null ? Integer.parseInt(result) : 0; + return result != null ? Integer.parseInt(result) : defaultValue; } return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); } - private void putBoolean(final String key, final boolean value) { + private void putBoolean(String key, boolean value) { putInt(key, value ? 1 : 0); } - private boolean getBoolean(final String key, final boolean defaultValue) { + private boolean getBoolean(String key, boolean defaultValue) { return getInt(key, defaultValue ? 1 : 0) == 1; } @@ -893,12 +901,12 @@ final class InputMethodUtils { } } - public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { + ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { return createEnabledInputMethodListLocked( getEnabledInputMethodsAndSubtypeListLocked()); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeListLocked(imi); @@ -909,8 +917,7 @@ final class InputMethodUtils { return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( - InputMethodInfo imi) { + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { List<Pair<String, ArrayList<String>>> imsList = getEnabledInputMethodsAndSubtypeListLocked(); ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); @@ -934,13 +941,13 @@ final class InputMethodUtils { return enabledSubtypes; } - public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), mInputMethodSplitter, mSubtypeSplitter); } - public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { + void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { if (reloadInputMethodStr) { getEnabledInputMethodsStr(); } @@ -957,7 +964,7 @@ final class InputMethodUtils { * Build and put a string of EnabledInputMethods with removing specified Id. * @return the specified id was removed or not. */ - public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { boolean isRemoved = false; boolean needsAppendSeparator = false; @@ -1012,7 +1019,7 @@ final class InputMethodUtils { } @NonNull - public String getEnabledInputMethodsStr() { + String getEnabledInputMethodsStr() { mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); if (DEBUG) { Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache @@ -1080,12 +1087,12 @@ final class InputMethodUtils { } } - public Pair<String, String> getLastInputMethodAndSubtypeLocked() { + Pair<String, String> getLastInputMethodAndSubtypeLocked() { // Gets the first one from the history return getLastSubtypeForInputMethodLockedInternal(null); } - public String getLastSubtypeForInputMethodLocked(String imeId) { + String getLastSubtypeForInputMethodLocked(String imeId) { Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); if (ime != null) { return ime.second; @@ -1203,7 +1210,7 @@ final class InputMethodUtils { return history; } - public void putSelectedInputMethod(String imeId) { + void putSelectedInputMethod(String imeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mCurrentUserId); @@ -1211,7 +1218,7 @@ final class InputMethodUtils { putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); } - public void putSelectedSubtype(int subtypeId) { + void putSelectedSubtype(int subtypeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mCurrentUserId); @@ -1220,7 +1227,7 @@ final class InputMethodUtils { } @Nullable - public String getSelectedInputMethod() { + String getSelectedInputMethod() { final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); if (DEBUG) { Slog.d(TAG, "getSelectedInputMethodStr: " + imi); @@ -1228,7 +1235,7 @@ final class InputMethodUtils { return imi; } - public boolean isSubtypeSelected() { + boolean isSubtypeSelected() { return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; } @@ -1236,11 +1243,11 @@ final class InputMethodUtils { return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); } - public boolean isShowImeWithHardKeyboardEnabled() { + boolean isShowImeWithHardKeyboardEnabled() { return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false); } - public void setShowImeWithHardKeyboard(boolean show) { + void setShowImeWithHardKeyboard(boolean show) { putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show); } @@ -1249,7 +1256,7 @@ final class InputMethodUtils { return mCurrentUserId; } - public int getSelectedInputMethodSubtypeId(String selectedImiId) { + int getSelectedInputMethodSubtypeId(String selectedImiId) { final InputMethodInfo imi = mMethodMap.get(selectedImiId); if (imi == null) { return NOT_A_SUBTYPE_ID; @@ -1258,8 +1265,8 @@ final class InputMethodUtils { return getSubtypeIdFromHashCode(imi, subtypeHashCode); } - public void saveCurrentInputMethodAndSubtypeToHistory( - String curMethodId, InputMethodSubtype currentSubtype) { + void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, + InputMethodSubtype currentSubtype) { String subtypeId = NOT_A_SUBTYPE_ID_STR; if (currentSubtype != null) { subtypeId = String.valueOf(currentSubtype.hashCode()); @@ -1277,8 +1284,8 @@ final class InputMethodUtils { } } - public static boolean isSoftInputModeStateVisibleAllowed( - int targetSdkVersion, @StartInputFlags int startInputFlags) { + static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion, + @StartInputFlags int startInputFlags) { if (targetSdkVersion < Build.VERSION_CODES.P) { // for compatibility. return true; diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 7625aafd0907..c56db7116819 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -2040,6 +2040,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "MSG_CHECK_JOB"); } + removeMessages(MSG_CHECK_JOB); if (mReportedActive) { // if jobs are currently being run, queue all ready jobs for execution. queueReadyJobsForExecutionLocked(); @@ -2099,7 +2100,6 @@ public class JobSchedulerService extends com.android.server.SystemService } maybeRunPendingJobsLocked(); // Don't remove JOB_EXPIRED in case one came along while processing the queue. - removeMessages(MSG_CHECK_JOB); } } } 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/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 1541b1d520cd..dd26a29d55af 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -42,8 +42,6 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; -import android.media.MediaController2; -import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.ControllerLink; import android.media.session.IActiveSessionsListener; @@ -60,7 +58,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -1007,17 +1004,50 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { if (DEBUG) { Log.d(TAG, "Session2 is created " + sessionToken); } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } + if (uid != sessionToken.getUid()) { + throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + + " but actually=" + sessionToken.getUid()); + } + int userId = UserHandle.getUserId(uid); + List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId); + if (session2Tokens.contains(sessionToken)) { + if (DEBUG) { + Log.d(TAG, "notifySession2Created(): Ignoring already existing token " + + sessionToken); + } + return; + } + session2Tokens.add(sessionToken); + pushSession2TokensChangedLocked(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + if (DEBUG) { + Log.d(TAG, "Session2 is destroyed " + sessionToken); + } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } if (uid != sessionToken.getUid()) { throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + " but actually=" + sessionToken.getUid()); } - Controller2Callback callback = new Controller2Callback(sessionToken); - // Note: It's safe not to keep controller here because it wouldn't be GC'ed until - // it's closed. - // TODO: Keep controller as well for better readability - // because the GC behavior isn't straightforward. - MediaController2 controller = new MediaController2(mContext, sessionToken, - new HandlerExecutor(mHandler), callback); + int userId = UserHandle.getUserId(uid); + mSession2TokensPerUser.get(userId).remove(sessionToken); + pushSession2TokensChangedLocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -2114,30 +2144,4 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); } } - - private class Controller2Callback extends MediaController2.ControllerCallback { - private final Session2Token mToken; - - Controller2Callback(Session2Token token) { - mToken = token; - } - - @Override - public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).add(mToken); - pushSession2TokensChangedLocked(userId); - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).remove(mToken); - pushSession2TokensChangedLocked(userId); - } - } - } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a1646862de9f..b81e2007c939 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -121,6 +121,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Resources; @@ -1087,7 +1088,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 +1110,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 +1670,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); @@ -3840,6 +3860,24 @@ public class NotificationManagerService extends SystemService { return mLockScreenAllowSecureNotifications; } + @Override + public boolean isPackagePaused(String pkg) { + Preconditions.checkNotNull(pkg); + checkCallerIsSameApp(pkg); + + boolean isPaused; + + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + int flags = pmi.getDistractingPackageRestrictions( + pkg, Binder.getCallingUserHandle().getIdentifier()); + isPaused = ((flags & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0); + + isPaused |= isPackageSuspendedForUser(pkg, Binder.getCallingUid()); + + return isPaused; + } + private void verifyPrivilegedListener(INotificationListener token, UserHandle user, boolean assistantAllowed) { ManagedServiceInfo info; @@ -7743,6 +7781,20 @@ public class NotificationManagerService extends SystemService { mPackageIntentReceiver.onReceive(getContext(), intent); } + @VisibleForTesting + protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { + // only use for testing: mimic receive broadcast that package is (un)distracting + // but does not actually register that info with packagemanager + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); + + final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + /** * Wrapper for a StatusBarNotification object that allows transfer across a oneway * binder without sending large amounts of data over a oneway transaction. diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 02fc51f89e62..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())); @@ -690,6 +690,8 @@ public final class NotificationRecord { importance)); } } + // We have now gotten all the information out of the adjustments and can forget them. + mAdjustments.clear(); } } diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 3d88f20f0710..2aaa1edcfad9 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -176,6 +176,14 @@ public class NotificationShellCmd extends ShellCommand { // only use for testing mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired()); } + case "distract_package": { + // only use for testing + // Flag values are in + // {@link android.content.pm.PackageManager.DistractionRestriction}. + mDirectService.simulatePackageDistractionBroadcast( + Integer.parseInt(getNextArgRequired()), + getNextArgRequired().split(",")); + } break; case "post": case "notify": diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index d0ef4f1523d4..d5089cb41dd4 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -696,7 +696,7 @@ public class LauncherAppsService extends SystemService { return null; } return new LauncherApps.AppUsageLimit( - data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining()); + data.getTotalUsageLimit(), data.getUsageRemaining()); } private void ensureShortcutPermission(@NonNull String callingPackage) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 0ab2a7361ac0..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(); @@ -537,7 +552,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false, - false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR); + false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR, + ""); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -1125,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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index b8825bbd2d72..494ec3ff67aa 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -155,6 +155,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_IS_FAILED = "isFailed"; private static final String ATTR_IS_APPLIED = "isApplied"; private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode"; + private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -267,6 +268,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private boolean mStagedSessionFailed; @GuardedBy("mLock") private int mStagedSessionErrorCode = SessionInfo.NO_ERROR; + @GuardedBy("mLock") + private String mStagedSessionErrorMessage; /** * Path to the validated base APK for this session, which may point at an @@ -413,7 +416,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, - boolean isFailed, boolean isApplied, int stagedSessionErrorCode) { + boolean isFailed, boolean isApplied, int stagedSessionErrorCode, + String stagedSessionErrorMessage) { mCallback = callback; mContext = context; mPm = pm; @@ -447,6 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; mStagedSessionErrorCode = stagedSessionErrorCode; + mStagedSessionErrorMessage = + stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; if (sealed) { synchronized (mLock) { try { @@ -499,7 +505,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isSessionApplied = mStagedSessionApplied; info.isSessionReady = mStagedSessionReady; info.isSessionFailed = mStagedSessionFailed; - info.setStagedSessionErrorCode(mStagedSessionErrorCode); + info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); } return info; } @@ -1971,17 +1977,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = false; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } /** {@hide} */ - void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) { + void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, + String errorMessage) { synchronized (mLock) { mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; + Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage); } mCallback.onStagedSessionChanged(this); } @@ -1993,6 +2003,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = true; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } @@ -2017,6 +2028,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return mStagedSessionErrorCode; } + /** {@hide} */ + String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + private void destroyInternal() { synchronized (mLock) { mSealed = true; @@ -2133,6 +2149,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed); writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied); writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode); + writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE, + mStagedSessionErrorMessage); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -2253,6 +2271,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED); final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE, SessionInfo.NO_ERROR); + final String stagedSessionErrorMessage = readStringAttribute(in, + ATTR_STAGED_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { throw new IllegalArgumentException("Can't restore staged session with invalid state."); @@ -2296,7 +2316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, - stagedSessionErrorCode); + stagedSessionErrorCode, stagedSessionErrorMessage); } /** diff --git a/services/core/java/com/android/server/pm/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/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 5311c2a55931..c4d27e5882c4 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -153,6 +153,19 @@ public class StagingManager { return success; } + private static boolean sendMarkStagedSessionReadyRequest(int sessionId) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } + private static boolean isApexSession(@NonNull PackageInstallerSession session) { return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; } @@ -166,6 +179,7 @@ public class StagingManager { if (!session.isMultiPackage() && isApexSession(session)) { success = submitSessionToApexService(session, null, apexInfoList); + } else if (session.isMultiPackage()) { List<PackageInstallerSession> childSessions = Arrays.stream(session.getChildSessionIds()) @@ -179,7 +193,13 @@ public class StagingManager { } // else this is a staged multi-package session with no APEX files. } - if (success && (apexInfoList.apexInfos.length > 0)) { + if (!success) { + session.setStagedSessionFailed( + SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more details."); + } + + if (apexInfoList.apexInfos.length > 0) { // For APEXes, we validate the signature here before we mark the session as ready, // so we fail the session early if there is a signature mismatch. For APKs, the // signature verification will be done by the package manager at the point at which @@ -190,16 +210,22 @@ public class StagingManager { for (ApexInfo apexPackage : apexInfoList.apexInfos) { if (!validateApexSignatureLocked(apexPackage.packagePath, apexPackage.packageName)) { - success = false; - break; + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APK-container signature verification failed for package " + + apexPackage.packageName + ". Signature of file " + + apexPackage.packagePath + " does not match the signature of " + + " the package already installed."); + // TODO(b/118865310): abort the session on apexd. + return; } } } - if (success) { - session.setStagedSessionReady(); - } else { - session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + session.setStagedSessionReady(); + if (!sendMarkStagedSessionReadyRequest(session.sessionId)) { + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more " + + "details."); } } @@ -217,13 +243,20 @@ public class StagingManager { return; } if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) { - session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED); + session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED, + "APEX activation failed. Check logcat messages from apexd for " + + "more information."); + } + if (apexSessionInfo.isVerified) { + // Session has been previously submitted to apexd, but didn't complete all the + // pre-reboot verification, perhaps because the device rebooted in the meantime. + // Greedily re-trigger the pre-reboot verification. + mBgHandler.post(() -> preRebootVerification(session)); } if (apexSessionInfo.isActivated) { session.setStagedSessionApplied(); // TODO(b/118865310) if multi-package proceed with the installation of APKs. } - // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ } // In every other case apexd will retry to apply the session at next boot. } diff --git a/services/core/java/com/android/server/pm/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 3a077b86b89a..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) { @@ -827,6 +833,8 @@ class ActivityStarter { final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags + | FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -901,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); } @@ -925,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) { @@ -973,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; } /** @@ -1630,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/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 73e96134167a..742ae41ed9f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -173,7 +173,8 @@ public class PackageInstallerSessionTest { /* isReady */ staged ? true : false, /* isFailed */ false, /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED); + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED, + /* stagedSessionErrorMessage */ "some error"); } private void dumpSession(PackageInstallerSession session) { @@ -295,6 +296,8 @@ public class PackageInstallerSessionTest { assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); + assertEquals(expected.getStagedSessionErrorMessage(), + actual.getStagedSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isSealed(), actual.isSealed()); assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java new file mode 100644 index 000000000000..9f1cbcd7ec27 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.attention.AttentionManagerInternal; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class AttentionDetectorTest extends AndroidTestCase { + + private @Mock AttentionManagerInternal mAttentionManagerInternal; + private @Mock Runnable mOnUserAttention; + private TestableAttentionDetector mAttentionDetector; + private long mAttentionTimeout; + private long mNextDimming; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any())) + .thenReturn(true); + mAttentionDetector = new TestableAttentionDetector(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE); + mAttentionDetector.setAttentionServiceSupported(true); + mNextDimming = SystemClock.uptimeMillis() + 3000L; + } + + @Test + public void testOnUserActivity_checksAttention() { + long when = registerAttention(); + verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnUserActivity_doesntCheckIfNotSupported() { + mAttentionDetector.setAttentionServiceSupported(false); + long when = registerAttention(); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(mNextDimming).isEqualTo(when); + } + + @Test + public void onUserActivity_ignoresWhiteListedActivityTypes() { + for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) { + int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i); + if (result == -1) { + throw new AssertionError("User activity " + i + " isn't listed in" + + " AttentionDetector#onUserActivity. Please consider how this new activity" + + " type affects the attention service."); + } + } + } + + @Test + public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() { + long now = SystemClock.uptimeMillis(); + mNextDimming = now; + mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(mNextDimming + 5000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_ignoresAfterMaximumExtension() { + long now = SystemClock.uptimeMillis(); + mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(now + 2000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_skipsIfAlreadyScheduled() { + registerAttention(); + reset(mAttentionManagerInternal); + long when = mAttentionDetector.updateUserActivity(mNextDimming); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() { + registerAttention(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP); + verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt()); + } + + @Test + public void testCallbackOnSuccess_ignoresIfNoAttention() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + @Test + public void testCallbackOnSuccess_callsCallback() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention).run(); + } + + @Test + public void testCallbackOnFailure_unregistersCurrentRequestCode() { + registerAttention(); + mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_FAILURE_UNKNOWN); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + private long registerAttention() { + mAttentionTimeout = 4000L; + mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH); + return mAttentionDetector.updateUserActivity(mNextDimming); + } + + private class TestableAttentionDetector extends AttentionDetector { + + private boolean mAttentionServiceSupported; + + TestableAttentionDetector() { + super(AttentionDetectorTest.this.mOnUserAttention, new Object()); + mAttentionManager = mAttentionManagerInternal; + mMaximumExtensionMillis = 10000L; + } + + void setAttentionServiceSupported(boolean supported) { + mAttentionServiceSupported = supported; + } + + @Override + public boolean isAttentionServiceSupported() { + return mAttentionServiceSupported; + } + + @Override + public long getAttentionTimeout() { + return mAttentionTimeout; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java index 5d69bbdcf0c7..4e43d002d4ae 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java @@ -964,26 +964,6 @@ public class AppTimeLimitControllerTests { assertFalse(hasAppUsageObserver(UID, OBS_ID1)); } - /** Verify app usage limit observer added correctly reports it being a group limit */ - @Test - public void testAppUsageLimitObserver_IsGroupLimit() { - addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN); - AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); - assertNotNull("Observer wasn't added", group); - assertTrue("Observer didn't correctly report being a group limit", - group.isGroupLimit()); - } - - /** Verify app usage limit observer added correctly reports it being not a group limit */ - @Test - public void testAppUsageLimitObserver_IsNotGroupLimit() { - addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN); - AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1); - assertNotNull("Observer wasn't added", group); - assertFalse("Observer didn't correctly report not being a group limit", - group.isGroupLimit()); - } - /** Verify app usage limit observer added correctly reports its total usage limit */ @Test public void testAppUsageLimitObserver_GetTotalUsageLimit() { 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/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java index fa472e2575f0..873ada05bc27 100644 --- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java +++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java @@ -510,12 +510,9 @@ public class AppTimeLimitController { } class AppUsageLimitGroup extends UsageGroup { - private boolean mGroupLimit; - public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) { super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback); - mGroupLimit = observed.length > 1; } @Override @@ -529,11 +526,6 @@ public class AppTimeLimitController { } @GuardedBy("mLock") - boolean isGroupLimit() { - return mGroupLimit; - } - - @GuardedBy("mLock") long getTotaUsageLimit() { return mTimeLimitMs; } @@ -547,14 +539,6 @@ public class AppTimeLimitController { return mTimeLimitMs - mUsageTimeMs; } } - - @Override - @GuardedBy("mLock") - void dump(PrintWriter pw) { - super.dump(pw); - pw.print(" groupLimit="); - pw.print(mGroupLimit); - } } @@ -692,7 +676,7 @@ public class AppTimeLimitController { smallestGroup = otherGroup; } } - return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(), + return new UsageStatsManagerInternal.AppUsageLimitData( smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining()); } } diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java index c2e4581285fd..acf994610182 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java +++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java @@ -24,9 +24,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; -// TODO: fix this. either move this class into system server or add a dependency on -// these wm classes to libiorap-java and libiorap-java-tests (somehow). import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; @@ -113,12 +112,12 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected void writeToParcelImpl(Parcel p, int flags) { super.writeToParcelImpl(p, flags); - intent.writeToParcel(p, flags); + IntentProtoParcelable.write(p, intent, flags); } IntentStarted(Parcel p) { super(p); - intent = Intent.CREATOR.createFromParcel(p); + intent = IntentProtoParcelable.create(p); } } @@ -232,8 +231,7 @@ public abstract class AppLaunchEvent implements Parcelable { } public static class ActivityLaunchCancelled extends AppLaunchEvent { - public final @Nullable - @ActivityRecordProto byte[] activityRecordSnapshot; + public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot; public ActivityLaunchCancelled(@SequenceId long sequenceId, @Nullable @ActivityRecordProto byte[] snapshot) { @@ -352,7 +350,6 @@ public abstract class AppLaunchEvent implements Parcelable { ActivityLaunchCancelled.class, }; - // TODO: move to @ActivityRecordProto byte[] once we have unit tests. public static class ActivityRecordProtoParcelable { public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, int flags) { @@ -365,4 +362,31 @@ public abstract class AppLaunchEvent implements Parcelable { return data; } } + + public static class IntentProtoParcelable { + private static final int INTENT_PROTO_CHUNK_SIZE = 1024; + + public static void write(Parcel p, @NonNull Intent intent, int flags) { + // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, + // so create a new one every time. + final ProtoOutputStream protoOutputStream = + new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE); + // Write this data out as the top-most IntentProto (i.e. it is not a sub-object). + intent.writeToProto(protoOutputStream); + final byte[] bytes = protoOutputStream.getBytes(); + + p.writeByteArray(bytes); + } + + // TODO: Should be mockable for testing? + // We cannot deserialize in the platform because we don't have a 'readFromProto' + // code. + public static @NonNull Intent create(Parcel p) { + // This will "read" the correct amount of data, but then we discard it. + byte[] data = p.createByteArray(); + + // Never called by real code in a platform, this binder API is implemented only in C++. + return new Intent("<cannot deserialize IntentProto>"); + } + } } diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index 7fcad360b8fe..9a30b35f02a2 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -24,12 +24,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Handler; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityMetricsLaunchObserver; @@ -43,10 +47,20 @@ import com.android.server.wm.ActivityTaskManagerInternal; */ public class IorapForwardingService extends SystemService { - public static final boolean DEBUG = true; // TODO: read from a getprop? public static final String TAG = "IorapForwardingService"; + /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */ + public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + /** $> adb shell 'setprop iorapd.enable true' */ + private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true); + /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ + private static boolean WTF_CRASH = SystemProperties.getBoolean( + "iorapd.forwarding_service.wtf_crash", false); private IIorap mIorapRemote; + private final Object mLock = new Object(); + /** Handle onBinderDeath by periodically trying to reconnect. */ + private final Handler mHandler = + new BinderConnectionHandler(IoThread.getHandler().getLooper()); /** * Initializes the system service. @@ -58,7 +72,7 @@ public class IorapForwardingService extends SystemService { * @param context The system server context. */ public IorapForwardingService(Context context) { - super(context); + super(context); } //<editor-fold desc="Providers"> @@ -78,12 +92,40 @@ public class IorapForwardingService extends SystemService { @VisibleForTesting protected IIorap provideIorapRemote() { + IIorap iorap; try { - return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); + iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); } catch (ServiceManager.ServiceNotFoundException e) { - // TODO: how do we handle service being missing? - throw new AssertionError(e); + handleRemoteError(e); + return null; } + + try { + iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); + } catch (RemoteException e) { + handleRemoteError(e); + return null; + } + + return iorap; + } + + @VisibleForTesting + protected DeathRecipient provideDeathRecipient() { + return new DeathRecipient() { + @Override + public void binderDied() { + Log.w(TAG, "iorapd has died"); + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + }; + } + + @VisibleForTesting + protected boolean isIorapEnabled() { + // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process + // never comes up, so all binder connections will fail indefinitely. + return IS_ENABLED; } //</editor-fold> @@ -94,15 +136,128 @@ public class IorapForwardingService extends SystemService { Log.v(TAG, "onStart"); } + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + + private class BinderConnectionHandler extends Handler { + public BinderConnectionHandler(android.os.Looper looper) { + super(looper); + } + + public static final int MESSAGE_BINDER_CONNECT = 0; + + private int mAttempts = 0; + + @Override + public void handleMessage(android.os.Message message) { + switch (message.what) { + case MESSAGE_BINDER_CONNECT: + if (!retryConnectToRemoteAndConfigure(mAttempts)) { + mAttempts++; + } else { + mAttempts = 0; + } + break; + default: + throw new AssertionError("Unknown message: " + message.toString()); + } + } + } + + /** + * Handle iorapd shutdowns and crashes, by attempting to reconnect + * until the service is reached again. + * + * <p>The first connection attempt is synchronous, + * subsequent attempts are done by posting delayed tasks to the IoThread.</p> + * + * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. + */ + private boolean retryConnectToRemoteAndConfigure(int attempts) { + final int sleepTime = 1000; // ms + + if (DEBUG) { + Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); + } + + if (connectToRemoteAndConfigure()) { + return true; + } + + // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually + // called 'adb shell stop iorapd' , which means this would loop until it comes back + // up. + // + // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid + // printing this warning. + Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); + + // Use a handler instead of Thread#sleep to avoid backing up the binder thread + // when this is called from the death recipient callback. + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), + sleepTime); + + return false; + + // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); + } + + private boolean connectToRemoteAndConfigure() { + synchronized (mLock) { + // Synchronize against any concurrent calls to this via the DeathRecipient. + return connectToRemoteAndConfigureLocked(); + } + } + + private boolean connectToRemoteAndConfigureLocked() { + if (!isIorapEnabled()) { + if (DEBUG) { + Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); + } + // When we see that iorapd is disabled (when system server comes up), + // it stays disabled permanently until the next system server reset. + + // TODO: consider listening to property changes as a callback, then we can + // be more dynamic about handling enable/disable. + return true; + } + // Connect to the native binder service. mIorapRemote = provideIorapRemote(); + if (mIorapRemote == null) { + Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); + return false; + } invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) ); + registerInProcessListenersLocked(); + + return true; + } + + private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); + private boolean mRegisteredListeners = false; + + private void registerInProcessListenersLocked() { + if (mRegisteredListeners) { + // Listeners are registered only once (idempotent operation). + // + // Today listeners are tolerant of the remote side going away + // by handling remote errors. + // + // We could try to 'unregister' the listener when we get a binder disconnect, + // but we'd still have to handle the case of encountering synchronous errors so + // it really wouldn't be a win (other than having less log spew). + return; + } // Listen to App Launch Sequence events from ActivityTaskManager, // and forward them to the native binder service. ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); - launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver()); + launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); + + mRegisteredListeners = true; } private class AppLaunchObserver implements ActivityMetricsLaunchObserver { @@ -110,6 +265,8 @@ public class IorapForwardingService extends SystemService { // launch sequences on the native side. private @AppLaunchEvent.SequenceId long mSequenceId = -1; + // All callbacks occur on the same background thread. Don't synchronize explicitly. + @Override public void onIntentStarted(@NonNull Intent intent) { // #onIntentStarted [is the only transition that] initiates a new launch sequence. @@ -174,7 +331,7 @@ public class IorapForwardingService extends SystemService { invokeRemote(() -> mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity)) + new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity)) ); } } @@ -201,6 +358,7 @@ public class IorapForwardingService extends SystemService { } } + /** Allow passing lambdas to #invokeRemote */ private interface RemoteRunnable { void run() throws RemoteException; } @@ -209,8 +367,26 @@ public class IorapForwardingService extends SystemService { try { r.run(); } catch (RemoteException e) { - // TODO: what do we do with exceptions? - throw new AssertionError("not implemented", e); + // This could be a logic error (remote side returning error), which we need to fix. + // + // This could also be a DeadObjectException in which case its probably just iorapd + // being manually restarted. + // + // Don't make any assumption, since DeadObjectException could also mean iorapd crashed + // unexpectedly. + // + // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. + handleRemoteError(e); } } + + private static void handleRemoteError(Throwable t) { + if (WTF_CRASH) { + // In development modes, we just want to crash. + throw new AssertionError("unexpected remote error", t); + } else { + // Log to wtf which gets sent to dropbox, and in system_server this does not crash. + Log.wtf(TAG, t); + } + } } diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml index f83a16ec0916..919154d3e48a 100644 --- a/startop/iorap/tests/AndroidTest.xml +++ b/startop/iorap/tests/AndroidTest.xml @@ -33,6 +33,15 @@ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"> </target_preparer> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- Crash instead of using Log.wtf within the system_server iorap code. --> + <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" /> + <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status --> + <option name="set-property" key="iorapd.binder.fake" value="true" /> + <option name="restore-properties" value="true" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.google.android.startop.iorap.tests" /> <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> diff --git a/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/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index 07c4a938cf9f..c16efbda1830 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { R.id.benchmark_text_low_hitrate, R.id.benchmark_edit_text_input, R.id.benchmark_overdraw, + R.id.benchmark_bitmap_upload, }; public static class LocalBenchmarksList extends ListFragment { @@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { case R.id.benchmark_text_low_hitrate: case R.id.benchmark_edit_text_input: case R.id.benchmark_overdraw: + case R.id.benchmark_bitmap_upload: case R.id.benchmark_memory_bandwidth: case R.id.benchmark_memory_latency: case R.id.benchmark_power_management: @@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { intent = new Intent(getApplicationContext(), EditTextInputActivity.class); break; case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class); + break; + case R.id.benchmark_bitmap_upload: intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); break; case R.id.benchmark_memory_bandwidth: diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java index 89c6aedd8b5c..5723c599d91a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -229,6 +229,8 @@ public class BenchmarkRegistry { return context.getString(R.string.cpu_gflops_name); case R.id.benchmark_overdraw: return context.getString(R.string.overdraw_name); + case R.id.benchmark_bitmap_upload: + return context.getString(R.string.bitmap_upload_name); default: return "Some Benchmark"; } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java index 787090208d7e..7692836cfacc 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; import com.android.benchmark.ui.automation.Automator; import com.android.benchmark.ui.automation.Interaction; @@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity { final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); - mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload); + + mAutomator = new Automator(name, runId, iteration, getWindow(), new Automator.AutomateCallback() { @Override public void onPostAutomate() { diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml index 6801fd9f61ec..694e0d9a6917 100644 --- a/tests/JankBench/app/src/main/res/values/ids.xml +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -23,6 +23,7 @@ <item name="benchmark_text_low_hitrate" type="id" /> <item name="benchmark_edit_text_input" type="id" /> <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_bitmap_upload" type="id" /> <item name="benchmark_memory_bandwidth" type="id" /> <item name="benchmark_memory_latency" type="id" /> <item name="benchmark_power_management" type="id" /> diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml index 270adf89e4ed..5c2405899db9 100644 --- a/tests/JankBench/app/src/main/res/values/strings.xml +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ <string name="edit_text_input_description">Tests edit text input</string> <string name="overdraw_name">Overdraw Test</string> <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="bitmap_upload_name">Bitmap Upload Test</string> + <string name="bitmap_upload_description">Tests bitmap upload</string> <string name="memory_bandwidth_name">Memory Bandwidth</string> <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> <string name="memory_latency_name">Memory Latency</string> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml index 07c453c25359..fccc7b9d3776 100644 --- a/tests/JankBench/app/src/main/res/xml/benchmark.xml +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -62,6 +62,12 @@ benchmark:category="ui" benchmark:description="@string/overdraw_description" /> + <com.android.benchmark.Benchmark + benchmark:name="@string/bitmap_upload_name" + benchmark:id="@id/benchmark_bitmap_upload" + benchmark:category="ui" + benchmark:description="@string/bitmap_upload_description" /> + <!-- <com.android.benchmark.Benchmark benchmark:name="@string/memory_bandwidth_name" diff --git a/tests/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/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 35fba3dcf7cf..488de8789178 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.net.NetworkInfo.DetailedState; @@ -120,8 +121,14 @@ public class WifiInfo implements Parcelable { @UnsupportedAppUsage private String mMacAddress = DEFAULT_MAC_ADDRESS; + /** + * Whether the network is ephemeral or not. + */ private boolean mEphemeral; + /** + * Whether the network is trusted or not. + */ private boolean mTrusted; /** @@ -130,6 +137,12 @@ public class WifiInfo implements Parcelable { private boolean mOsuAp; /** + * If connected to a network suggestion or specifier, store the package name of the app, + * else null. + */ + private String mNetworkSuggestionOrSpecifierPackageName; + + /** * Running total count of lost (not ACKed) transmitted unicast data packets. * @hide */ @@ -209,6 +222,7 @@ public class WifiInfo implements Parcelable { setMeteredHint(false); setEphemeral(false); setOsuAp(false); + setNetworkSuggestionOrSpecifierPackageName(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -240,6 +254,8 @@ public class WifiInfo implements Parcelable { mMeteredHint = source.mMeteredHint; mEphemeral = source.mEphemeral; mTrusted = source.mTrusted; + mNetworkSuggestionOrSpecifierPackageName = + source.mNetworkSuggestionOrSpecifierPackageName; mOsuAp = source.mOsuAp; txBad = source.txBad; txRetries = source.txRetries; @@ -476,6 +492,17 @@ public class WifiInfo implements Parcelable { return mOsuAp; } + /** {@hide} */ + public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) { + mNetworkSuggestionOrSpecifierPackageName = packageName; + } + + /** {@hide} */ + public @Nullable String getNetworkSuggestionOrSpecifierPackageName() { + return mNetworkSuggestionOrSpecifierPackageName; + } + + /** @hide */ @UnsupportedAppUsage public void setNetworkId(int id) { @@ -634,6 +661,7 @@ public class WifiInfo implements Parcelable { dest.writeDouble(rxSuccessRate); mSupplicantState.writeToParcel(dest, flags); dest.writeInt(mOsuAp ? 1 : 0); + dest.writeString(mNetworkSuggestionOrSpecifierPackageName); } /** Implement the Parcelable interface {@hide} */ @@ -672,6 +700,7 @@ public class WifiInfo implements Parcelable { info.rxSuccessRate = in.readDouble(); info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); info.mOsuAp = in.readInt() != 0; + info.mNetworkSuggestionOrSpecifierPackageName = in.readString(); return info; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8086039799e5..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 @@ -1404,7 +1401,6 @@ public class WifiManager { * {@link #reject()} to return the user's selection back to the platform via this callback. * @hide */ - @SystemApi public interface NetworkRequestUserSelectionCallback { /** * User selected this network to connect to. @@ -1428,7 +1424,6 @@ public class WifiManager { * or reject the request by the app. * @hide */ - @SystemApi public interface NetworkRequestMatchCallback { /** * Invoked to register a callback to be invoked to convey user selection. The callback @@ -1605,7 +1600,6 @@ public class WifiManager { * object. If null, then the application's main thread will be used. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback, @Nullable Handler handler) { @@ -1635,7 +1629,6 @@ public class WifiManager { * @param callback Callback for network match events * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback( @NonNull NetworkRequestMatchCallback callback) { @@ -1724,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 { @@ -1741,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 @@ -1766,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 diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index 677bf371c781..948dcfa47f59 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -35,6 +35,7 @@ public class WifiInfoTest { private static final long TEST_TX_RETRIES = 2; private static final long TEST_TX_BAD = 3; private static final long TEST_RX_SUCCESS = 4; + private static final String TEST_PACKAGE_NAME = "com.test.example"; /** * Verify parcel write/read with WifiInfo. @@ -48,6 +49,7 @@ public class WifiInfoTest { writeWifiInfo.rxSuccess = TEST_RX_SUCCESS; writeWifiInfo.setTrusted(true); writeWifiInfo.setOsuAp(true); + writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME); Parcel parcel = Parcel.obtain(); writeWifiInfo.writeToParcel(parcel, 0); @@ -62,5 +64,6 @@ public class WifiInfoTest { assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess); assertTrue(readWifiInfo.isTrusted()); assertTrue(readWifiInfo.isOsuAp()); + assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName()); } } |