Merge "Pipe IME state into insets (IME transitions 3/n)"
diff --git a/Android.bp b/Android.bp
index a5cc89c..afdcadd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -271,6 +271,7 @@
"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 @@
"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 @@
"ext",
],
- jarjar_rules: ":framework-hidl-jarjar",
+ jarjar_rules: "jarjar_rules_hidl.txt",
static_libs: [
"apex_aidl_interface-java",
@@ -738,6 +740,15 @@
"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 @@
"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 {
- 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 @@
}
// 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 @@
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 a7a81f2..767434d 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 @@
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 08b0e03..2692916 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5101,7 +5101,7 @@
}
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 +5466,11 @@
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 +5479,11 @@
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);
}
@@ -12508,6 +12512,7 @@
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);
@@ -12531,6 +12536,7 @@
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
@@ -13943,8 +13949,6 @@
@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);
@@ -16494,6 +16498,7 @@
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);
@@ -23518,6 +23523,8 @@
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);
@@ -34401,8 +34408,12 @@
}
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();
@@ -38638,6 +38649,7 @@
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";
@@ -38671,6 +38683,7 @@
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";
@@ -38745,6 +38758,7 @@
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";
}
@@ -41400,9 +41414,9 @@
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";
}
@@ -44657,12 +44671,14 @@
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);
@@ -44725,6 +44741,11 @@
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);
@@ -48563,6 +48584,7 @@
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);
@@ -53341,8 +53363,8 @@
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 {
@@ -55926,10 +55948,10 @@
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 {
@@ -62381,20 +62403,20 @@
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 64b3d6b..328b6d2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -508,11 +508,13 @@
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 @@
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 @@
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 @@
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
method public int getRollbackId();
+ method public int getSessionId();
+ method public boolean isStaged();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
- field public final android.content.rollback.PackageRollbackInfo targetPackage;
}
public final class RollbackManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
}
@@ -4101,25 +4105,10 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
}
- public final class IpSecTransform implements java.lang.AutoCloseable {
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void startNattKeepalive(@NonNull android.net.IpSecTransform.NattKeepaliveCallback, int, @NonNull android.os.Handler) throws java.io.IOException;
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void stopNattKeepalive();
- }
-
public static class IpSecTransform.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
}
- public static class IpSecTransform.NattKeepaliveCallback {
- ctor public IpSecTransform.NattKeepaliveCallback();
- method public void onError(int);
- method public void onStarted();
- method public void onStopped();
- field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3
- field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2
- field public static final int ERROR_INVALID_NETWORK = 1; // 0x1
- }
-
public class LinkAddress implements android.os.Parcelable {
ctor public LinkAddress(java.net.InetAddress, int, int, int);
ctor public LinkAddress(java.net.InetAddress, int);
@@ -4249,6 +4238,24 @@
field public final android.net.RssiCurve rssiCurve;
}
+ public final class StaticIpConfiguration implements android.os.Parcelable {
+ ctor public StaticIpConfiguration();
+ ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+ method public void addDnsServer(java.net.InetAddress);
+ method public void clear();
+ method public int describeContents();
+ method public java.util.List<java.net.InetAddress> getDnsServers();
+ method public String getDomains();
+ method public java.net.InetAddress getGateway();
+ method public android.net.LinkAddress getIpAddress();
+ method public java.util.List<android.net.RouteInfo> getRoutes(String);
+ method public void setDomains(String);
+ method public void setGateway(java.net.InetAddress);
+ method public void setIpAddress(android.net.LinkAddress);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -4274,6 +4281,47 @@
}
+package android.net.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 {
@@ -4694,7 +4742,6 @@
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);
@@ -4704,7 +4751,6 @@
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
@@ -4741,19 +4787,6 @@
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);
}
@@ -5057,6 +5090,7 @@
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";
}
@@ -5074,6 +5108,36 @@
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;
}
@@ -5548,7 +5612,6 @@
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";
}
@@ -7864,7 +7927,7 @@
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();
@@ -7875,9 +7938,9 @@
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;
}
@@ -7891,8 +7954,8 @@
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();
@@ -8259,6 +8322,16 @@
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();
@@ -8276,7 +8349,7 @@
}
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();
@@ -8285,8 +8358,8 @@
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);
@@ -8721,13 +8794,13 @@
}
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 0f2ba12..049e002 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,6 +6,7 @@
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 @@
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.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 @@
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 @@
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.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 @@
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 @@
}
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 1315750..deebd4e 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 9c320d3..b26c713 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -989,6 +989,25 @@
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 fe0504f..cdff50f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -132,6 +132,17 @@
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 d6b4737f..8e56bef 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -28,6 +28,7 @@
import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
import "frameworks/base/core/proto/android/debug/enums.proto";
+import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto";
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -146,9 +147,9 @@
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 +164,7 @@
// 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 +210,13 @@
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;
}
// 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 +249,7 @@
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 +264,9 @@
BatteryLevel battery_level = 10043;
BuildInformation build_information = 10044;
BatteryCycleCount battery_cycle_count = 10045;
+ DebugElapsedClock debug_elapsed_clock = 10046;
+ DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
+ NumBiometricsEnrolled num_faces_enrolled = 10048;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -1483,6 +1490,25 @@
optional android.bluetooth.hci.StatusEnum reason_code = 9;
}
+/**
+ * Logs when a module is rolled back by Watchdog.
+ *
+ * Logged from: Rollback Manager
+ */
+message WatchdogRollbackOccurred {
+ enum RollbackType {
+ UNKNOWN = 0;
+ ROLLBACK_INITIATE = 1;
+ ROLLBACK_SUCCESS = 2;
+ ROLLBACK_FAILURE = 3;
+ }
+ optional RollbackType rollback_type = 1;
+
+ optional string package_name = 2;
+
+ optional int32 package_version_code = 3;
+}
+
/**
* Logs when something is plugged into or removed from the USB-C connector.
@@ -2351,58 +2377,95 @@
}
/**
- * 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
+ * frameworks/base/services/core/java/com/android/server/biometrics
*/
-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;
-}
+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;
-/**
- * Logs when a fingerprint error occurs.
- *
- * Logged from:
- * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
- */
-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 {
+ enum State {
UNKNOWN = 0;
- LOCKOUT = 1;
- PERMANENT_LOCKOUT = 2;
+ REJECTED = 1;
+ PENDING_CONFIRMATION = 2;
+ CONFIRMED = 3;
}
- // The type of error.
- optional Error error = 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 biometric error occurs.
+ *
+ * Logged from:
+ * 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 BiometricHalDeathReported {
+ // Biometric modality.
+ optional android.hardware.biometrics.ModalityEnum modality = 1;
}
message Notification {
@@ -2476,18 +2539,14 @@
}
/*
- * 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 +3408,14 @@
/**
* 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 +4616,86 @@
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;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 5fea90b..aa22333 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -128,6 +128,17 @@
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 @@
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 @@
}
mConfigReceivers.clear();
+ mActiveConfigsChangedReceivers.clear();
for (const sp<ConfigListener>& listener : mListeners) {
broadcastList.push_back(listener);
}
@@ -250,6 +267,17 @@
}
}
+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 122e669..c064a51 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -82,6 +82,23 @@
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 @@
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 7e56bee..ba7bcc4 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -175,8 +175,8 @@
{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 @@
{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 ee2fe8f..8e7a58b 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -901,7 +901,6 @@
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/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b556033..da45054 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -78,7 +78,7 @@
/**
* 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 @@
* @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/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 181acce..f522d71 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -29,6 +29,7 @@
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 @@
"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 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 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 b405d0c..7c550d4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -84,7 +84,6 @@
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 @@
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 @@
mTitle = in.readCharSequence();
mIcon = Icon.CREATOR.createFromParcel(in);
mDesiredHeight = in.readInt();
+ mFlags = in.readInt();
}
/**
@@ -8448,6 +8472,24 @@
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 @@
out.writeCharSequence(mTitle);
mIcon.writeToParcel(out, 0);
out.writeInt(mDesiredHeight);
+ out.writeInt(mFlags);
+ }
+
+ private void setFlags(int flags) {
+ mFlags = flags;
}
/**
@@ -8484,6 +8531,7 @@
private CharSequence mTitle;
private Icon mIcon;
private int mDesiredHeight;
+ private int mFlags;
/**
* Constructs a new builder object.
@@ -8543,6 +8591,39 @@
}
/**
+ * 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 @@
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/StatsManager.java b/core/java/android/app/StatsManager.java
index 9dcd122..9b66c92 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -73,6 +73,11 @@
*/
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 @@
}
}
+ /**
+ * 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 2dc225a..ee13164 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -102,6 +102,7 @@
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 @@
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 2964fbc..cf62e8d 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -18,6 +18,8 @@
import android.app.role.IOnRoleHoldersChangedListener;
import android.app.role.IRoleManagerCallback;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
/**
* @hide
@@ -52,4 +54,8 @@
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/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 171c2f5..c4bf1eb 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -434,7 +434,7 @@
* {@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 @@
* @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 78560d2..2cb7b2d 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 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 @@
* @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 b8670db..ef77596 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 @@
/** 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 6704a45..47a4a2d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -87,6 +87,7 @@
* <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 280f1ac..87f9e46 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3637,6 +3637,16 @@
/**
* 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 edd765b..22f73db 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2375,8 +2375,7 @@
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 @@
*/
@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 @@
}
/** @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 @@
if (mSelector != null) {
proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
}
- proto.end(token);
}
/**
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 2faf3ad..4f4c34b 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -27,5 +27,4 @@
*/
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 420bcb6..226d76a 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 0803a7c..812f995 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 @@
*/
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 @@
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 @@
@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 c1c0bc1..f8abcfc 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 @@
}
/**
- * 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 @@
* (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 @@
}
/**
- * 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 8bcaa45..b44458a 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 @@
@UnsupportedAppUsage
private Uri mNotifyUri;
+ private List<Uri> mNotifyUris;
private final Object mSelfObserverLock = new Object();
private ContentObserver mSelfObserver;
@@ -155,7 +159,11 @@
@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 @@
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 @@
*/
@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 @@
}
@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 5a4280e..1379138 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 @@
/**
* 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 @@
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 @@
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 0d27dfb..c9cafaf 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -21,6 +21,8 @@
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 @@
}
@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/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d99..209afb8 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,55 @@
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 8aac1bf..5afe1a9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -171,5 +171,39 @@
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 c69b68e4..ec62aba 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -77,6 +77,10 @@
* @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 @@
}
/**
+ * 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 @@
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 @@
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 de828f2..e4336d1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -51,4 +51,12 @@
// 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/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7e45441..f3ebd7f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,6 +20,7 @@
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 @@
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 @@
}
/**
+ * 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 aae8afb..5ea8bd6 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -116,4 +116,9 @@
// 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/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index b5d8226..6c291c2 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 @@
@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 @@
}
/**
+ * @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 void clear() {
- super.clear();
+ ipAddress = null;
+ gateway = null;
+ dnsServers.clear();
+ domains = null;
+ serverAddress = null;
vendorInfo = null;
leaseDuration = 0;
mtu = 0;
@@ -111,20 +151,20 @@
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 @@
/** 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 @@
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 23c8aa3..e519fdf 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -257,7 +257,6 @@
*
* @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 @@
*
* @hide
*/
- @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
@@ -331,7 +329,6 @@
*
* @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 c996d01..39db4fe 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -21,8 +21,10 @@
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.Locale;
import java.util.TreeSet;
-import android.system.ErrnoException;
-
/**
* Native methods for managing network interfaces.
*
@@ -177,119 +177,37 @@
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);
+ return Inet4AddressUtils.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));
- }
-
- /**
- * 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);
+ return Inet4AddressUtils.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));
- }
-
- /**
- * @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 @@
* @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 @@
*/
@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 @@
}
/**
- * 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 3aa56b9..25bae3c 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 @@
*
* @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 @@
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 @@
* 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 @@
* 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 @@
return lp;
}
+ @Override
public String toString() {
StringBuffer str = new StringBuffer();
@@ -143,6 +185,7 @@
return str.toString();
}
+ @Override
public int hashCode() {
int result = 13;
result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode());
@@ -168,12 +211,10 @@
}
/** 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 @@
};
/** 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 @@
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 @@
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 f28cdc9..73cf94b 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 1634694..7432687 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 57a926a..7ad4ecf 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
@@ -21,21 +21,26 @@
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 @@
* @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 @@
* <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 @@
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 0000000..bec0c84
--- /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 954071a..3fc5e41 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 @@
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 eae1aa5..1919fcc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -834,10 +834,10 @@
* 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 d463b44..3a5b8a8 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 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 @@
}
/**
- * An interface describing the listener for bugreport progress and status.
+ * An interface describing the callback for bugreport progress and status.
*/
- public interface BugreportListener {
+ public abstract static class BugreportCallback {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+ BUGREPORT_ERROR_INVALID_INPUT,
+ BUGREPORT_ERROR_RUNTIME,
+ BUGREPORT_ERROR_USER_DENIED_CONSENT,
+ BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT
+ })
+
+ /** Possible error codes taking a bugreport can encounter */
+ public @interface BugreportErrorCode {}
+
+ /** The input options were invalid */
+ public static final int BUGREPORT_ERROR_INVALID_INPUT =
+ IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+
+ /** A runtime error occured */
+ public static final int BUGREPORT_ERROR_RUNTIME =
+ IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
+
+ /** User denied consent to share the bugreport */
+ public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
+ IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
+
+ /** The request to get user consent timed out. */
+ public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+ IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
/**
* Called when there is a progress update.
* @param progress the progress in [0.0, 100.0]
*/
- void onProgress(float progress);
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
- BUGREPORT_ERROR_INVALID_INPUT,
- BUGREPORT_ERROR_RUNTIME
- })
-
- /** Possible error codes taking a bugreport can encounter */
- @interface BugreportErrorCode {}
-
- /** The input options were invalid */
- int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
-
- /** A runtime error occured */
- int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
-
- /** User denied consent to share the bugreport */
- 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 =
- IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+ 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 @@
* 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 @@
* @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 @@
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 @@
@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 4e696ae..3871375 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 @@
*
* @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 0000000..2629a40
--- /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 0000000..69ab1d9
--- /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 dd85e15..da03895 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 @@
* 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 @@
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);
- Integer i = new Integer(wfd);
- if (wfd >= 0) {
- synchronized (m_observers) {
- m_observers.put(i, new WeakReference(observer));
+ startWatching(m_fd, paths, mask, wfds);
+
+ 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 @@
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 @@
}
// 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 @@
*
* @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 @@
* 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 @@
* 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 51c3c4c..629289b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -39,6 +39,7 @@
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 @@
*
* @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 0000000..56da8c7
--- /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 0000000..666171f
--- /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 74d434c..93d6f4c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -119,6 +119,23 @@
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 630bd2e..d68eeed 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_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 @@
*
* @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 2c68ddd..5b45ac2 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 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 4ce760f..7f4254e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -336,6 +336,13 @@
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 94441ca..a96618a 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 @@
* @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 8d568c8..33795f8 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -78,15 +78,6 @@
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 @@
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 8bd75d7..8a52f1f 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -19,6 +19,7 @@
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 @@
*
* @hide
*/
+ @TestApi
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
ACCOUNT_NAME,
ACCOUNT_TYPE,
@@ -1832,6 +1834,7 @@
*
* @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 25554b9..81e1eb9 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -126,6 +126,7 @@
* 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 @@
* nothing will be done.
* @hide
*/
+ @TestApi
public static final String UNDEMOTE_METHOD = "undemote";
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5c442f..887175a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -104,6 +104,11 @@
*/
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 @@
*
* @hide
*/
+ @TestApi
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
@@ -3022,6 +3028,7 @@
*
* @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 d840e3c..f04c752 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -486,6 +486,37 @@
"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 @@
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 @@
* entity_list_default (String[])
* entity_list_not_editable (String[])
* entity_list_editable (String[])
+ * lang_id_threshold_override (float)
* </pre>
*
* <p>
@@ -14280,6 +14313,17 @@
*/
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 +14647,17 @@
"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 140336e..dce5d56 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -18,6 +18,7 @@
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 @@
* 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 b6788f5..93189b3 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -63,15 +63,17 @@
/**
* 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 2984ef6..db9351b 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -40,6 +40,7 @@
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 @@
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 @@
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 f58efc9..e3a6bd7 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
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 @@
}
/**
+ * 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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 863b717..4032a6b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -46,10 +46,9 @@
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 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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
* @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 @@
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 @@
}
/**
+ * 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 45e6c50..ecbec65 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -492,7 +492,7 @@
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 f1dfc1c..cd3decf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9067,35 +9067,43 @@
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));
- }
- 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;
- } 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)
+ + ": 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_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 + ": laid="
+ + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+ + ", visible=" + (getVisibility() == VISIBLE)
+ + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+ & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
+ & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
+ }
+ return;
+ }
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/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index 57ed7f9..7c79d44 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -93,8 +93,14 @@
* {@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 fdc34b3..4d917a1 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -115,7 +115,7 @@
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 f7c1a26..502181f 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -109,9 +109,9 @@
*
* @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 @@
*
* @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 @@
/**
* 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 ce680ec..7f928f7 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -46,6 +46,7 @@
* entity_list_default (String[])
* entity_list_not_editable (String[])
* entity_list_editable (String[])
+ * lang_id_threshold_override (float)
* </pre>
*
* <p>
@@ -94,6 +95,8 @@
"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 @@
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 @@
.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 @@
.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 @@
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 @@
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 @@
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 @@
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 a5b7c62..7782079 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -567,8 +567,9 @@
}
}
- // 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 @@
}
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 b1609fc..735c3eb 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -89,9 +89,10 @@
/**
* 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 @@
}
/**
- * 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 afe46701..5147306 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -359,7 +359,7 @@
/**
* @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 @@
/**
* @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 @@
* 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 @@
* (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 42acb09..803462d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -98,6 +102,20 @@
private static final boolean DEBUG = false;
+
+ /**
+ * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+ * {@link AppPredictionManager} will be queried for direct share targets.
+ */
+ // TODO(b/123089490): Replace with system flag
+ private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+ // TODO(b/123088566) Share these in a better way.
+ private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+ private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+ public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ private AppPredictor mAppPredictor;
+ private AppPredictor.Callback mAppPredictorCallback;
+
/**
* If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
* binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@
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 +386,10 @@
}
unbindRemainingServices();
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+ mAppPredictor.destroy();
+ }
}
@Override
@@ -606,15 +657,10 @@
}
}
- 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,42 +680,59 @@
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);
-
- // 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)));
- }
- }
- 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);
- }
+ sendShareShortcutInfoList(resultList, driList);
});
}
+ 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)));
+ }
+ }
+ 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) {
ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
Bundle extras = new Bundle();
@@ -724,6 +787,7 @@
// Do nothing. We'll send the voice stuff ourselves.
}
+ // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
void updateModelAndChooserCounts(TargetInfo info) {
if (info != null) {
final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 514ff76..d7514d1 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -154,4 +154,7 @@
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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 534361e..c6afee2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13395,11 +13395,22 @@
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 @@
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 6a28059..943c726 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -160,4 +160,9 @@
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 e5ad1f4..7398e95 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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
/**
* 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/jni/Android.bp b/core/jni/Android.bp
index be12700..0466aaa 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -275,6 +275,7 @@
"libselinux",
"libandroidicu",
"libmedia",
+ "libmedia_helper",
"libmediametrics",
"libmeminfo",
"libaudioclient",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index dd7633a..cc22ff0 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -29,6 +29,7 @@
#include "SkBlurDrawLooper.h"
#include "SkColorFilter.h"
#include "SkFont.h"
+#include "SkFontMetrics.h"
#include "SkFontTypes.h"
#include "SkMaskFilter.h"
#include "SkPath.h"
@@ -668,12 +669,12 @@
}
static jint getHinting(jlong paintHandle) {
- return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getHinting()
+ return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getSkFont().getHinting()
== kNo_SkFontHinting ? 0 : 1;
}
static void setHinting(jlong paintHandle, jint mode) {
- reinterpret_cast<Paint*>(paintHandle)->setHinting(
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setHinting(
mode == 0 ? kNo_SkFontHinting : kNormal_SkFontHinting);
}
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 1065738..a0d99ec 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>
@@ -1311,6 +1312,33 @@
}
// ----------------------------------------------------------------------------
+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 +1413,8 @@
(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},
};
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
deleted file mode 100644
index 38f7a7e..0000000
--- a/core/jni/android_media_MediaMetricsJNI.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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
-
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
new file mode 120000
index 0000000..3204317c
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.cpp
@@ -0,0 +1 @@
+../../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
deleted file mode 100644
index b3cb4d2..0000000
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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_
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
new file mode 120000
index 0000000..c7a685b
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -0,0 +1 @@
+../../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 6f975b2..d25192a 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 @@
#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 @@
/* 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_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 69877c7..f1b259e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -124,7 +124,7 @@
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 @@
}
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 @@
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 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 @@
(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_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8681d4b..6ee9606 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -821,7 +821,7 @@
// 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 @@
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 4e48663..4b37f13 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -423,13 +423,13 @@
}
void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
- const int dev_null_fd = open("/dev/null", O_RDWR);
+ const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (dev_null_fd < 0) {
fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- if (dup2(dev_null_fd, fd) == -1) {
- fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+ fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
fd,
strerror(errno)));
}
diff --git a/core/proto/android/hardware/biometrics/enums.proto b/core/proto/android/hardware/biometrics/enums.proto
new file mode 100644
index 0000000..91f2acb
--- /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 f06165c..7e7942e 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -522,6 +522,8 @@
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 aaf6c63..f3733fd 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -72,6 +72,9 @@
// 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a54bd7..25baa92 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -43,7 +43,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
- <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
+ <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
@@ -3329,7 +3329,7 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an application to clear user data.
+ <!-- @SystemApi @TestApi Allows an application to clear user data.
<p>Not for use by third-party applications
@hide
-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 49f2c84..c05795d 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 d556130..f79e22d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3554,4 +3554,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 8e8b07a..9d04e63 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -53,7 +53,6 @@
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 a15dbc8..bd7f852 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,7 @@
Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index 780e15a..5022e30 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 @@
@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 @@
.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 @@
@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 9662182..32bafec 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 @@
@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 @@
+ "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 @@
+ "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 4d78e40..5e58f82 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 @@
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 @@
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/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4755d45..c9e4694 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1821,6 +1821,8 @@
* @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 @@
* @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/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3c35d9b..20303eb 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -562,7 +562,7 @@
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 @@
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 645984d..c63dff8 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,12 +48,12 @@
!(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 eb6eb8e..e649940 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,12 +28,11 @@
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 105dcd2..447fdf5 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -210,6 +210,16 @@
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/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index d2903f0..2f2d575 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -99,7 +99,7 @@
////////////////// Java flags compatibility //////////////////
-/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
+/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
match up with skia anymore, so we have to do some shuffling in get/set flags()
3 flags apply to SkPaint (antialias, dither, filter -> enum)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b7f042b..f996d38 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -30,6 +30,7 @@
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 @@
}
/**
- * 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 @@
* {@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 af016d5..ffa3b24 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,6 +39,8 @@
*/
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 @@
}
}
+ /** 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 @@
@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 5e77fdf..3440cde 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -996,6 +996,50 @@
}
/**
+ * 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 @@
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 14bdab9..f5aeca7 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -151,8 +151,6 @@
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/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f..33e7d8e 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -335,7 +335,7 @@
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 @@
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 @@
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/jni/Android.bp b/media/jni/Android.bp
index 48dbf55..852d296 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -101,7 +101,7 @@
"libhidlbase",
"libhidlmemory",
- "libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API.
+ "libmediametrics",
"libbinder", // Used by JWakeLock and MediaMetrics.
"libutils", // Have to use shared lib to make libandroid_runtime behave correctly.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90..417a427 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -405,6 +405,11 @@
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 3ded8c2..de60b08 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 @@
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 @@
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 fd621ea..a10780f 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -27,6 +27,7 @@
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 9b4e730..3069161 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -786,22 +786,17 @@
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/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5fae9d5..3156732 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -47,32 +47,13 @@
static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
sp<SurfaceComposerClient> client = surfaceControl->getClient();
sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-
- Vector<ui::ColorMode> colorModes;
- status_t err = client->getDisplayColorModes(display, &colorModes);
+ bool isWideColorDisplay = false;
+ status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
if (err) {
ALOGE("unable to get wide color support");
return false;
}
-
- bool wideColorBoardConfig =
- getBool<ISurfaceFlingerConfigs,
- &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
-
- for (android::ui::ColorMode colorMode : colorModes) {
- switch (colorMode) {
- case ui::ColorMode::DISPLAY_P3:
- case ui::ColorMode::ADOBE_RGB:
- case ui::ColorMode::DCI_P3:
- if (wideColorBoardConfig) {
- return true;
- }
- break;
- default:
- break;
- }
- }
- return false;
+ return isWideColorDisplay;
}
static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 83084c5..7eaf04b 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.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.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 @@
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 f346b00..ce58d4e 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 @@
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 bc6e2fc..5acf4fb 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 @@
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 @@
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 707349b..7f8127a 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 @@
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 a2da0a0..9b0d896 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,6 +21,7 @@
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 0000000..55ba591
--- /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 50c4dfc..08452bb 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_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 @@
// 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 04ac9a3..12eecc0 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_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 @@
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 0d298de..0a15cd7 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 @@
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 ce8b7e7..d7ff98b1 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 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 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 @@
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 7b112df..beabd3e 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.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_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 @@
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 @@
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 @@
}
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 @@
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 868f3be..31ce95b 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.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 @@
*/
@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 ad7f85d..f20e0163 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.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 @@
// 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 eb993a4..2e6ff24 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.OsConstants;
import android.util.Log;
-import com.android.internal.util.BitUtils;
-
import libcore.io.IoUtils;
import java.io.FileDescriptor;
@@ -186,7 +184,7 @@
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 761db68..76a0338 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.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 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
similarity index 62%
rename from services/net/java/android/net/dhcp/DhcpClient.java
rename to packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
index cddb91f..6dcf0c0 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 0000000..d3b40a6
--- /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 0000000..14e6c5f
--- /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 4080ddf..631ee45 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.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 @@
*/
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 @@
* <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 @@
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 @@
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 6b31b82..96eaa50 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_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.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.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 @@
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 @@
}
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 eedaf30..804765e 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 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 51d50d9..4abd77e 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.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 @@
@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 @@
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 @@
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 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 @@
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 @@
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 @@
@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 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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 @@
// 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 @@
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 @@
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 @@
*/
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 a592809..7544e72 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.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.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 @@
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 3ca0564..1004382 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 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 @@
}
private static int[] toIntArray(Collection<Inet4Address> addrs) {
- return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray();
+ return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
}
private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aff6f04..a1aefab 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 @@
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 @@
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 0a62b7c..e0d178f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@
"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 @@
"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
new file mode 100644
index 0000000..c3a45e2
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
new file mode 100644
index 0000000..e77c676
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
new file mode 100644
index 0000000..a67b5e8
--- /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
+![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png)
+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
+![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png)
+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 0000000..47354d4
--- /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 31119a9..5652b49 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 204408cd..13186fc 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 0000000..e6f2376
--- /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 0000000..0c11cd9
--- /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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab0bbe1..d435c67 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 5e32dd5..6a68457 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 fd7a105..e8fabf5 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 190bd7a..ffa5de8 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 0c7a9a9..85265f4 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -26,11 +26,11 @@
* 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 92d3cc1..36a813b 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 @@
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 a457dee..b7bee30 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 @@
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 @@
}
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 @@
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 @@
}
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 @@
return mStackView;
}
- // TODO: factor in PIP location / maybe last place user had it
- /**
- * Gets an appropriate starting point to position the bubble stack.
- */
- private static Point getStartPoint(int size, Point displaySize) {
- final int x = displaySize.x - size + EDGE_OVERLAP;
- final int y = displaySize.y / 4;
- return new Point(x, y);
- }
-
- /**
- * Gets an appropriate position for the bubble when the stack is expanded.
- */
- static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
- // Same place for now..
- return new Point(EDGE_OVERLAP, size);
- }
-
/**
* Whether the notification has been developer configured to bubble and is allowed by the user.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
deleted file mode 100644
index c1063fa..0000000
--- 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 ae030cf..b584f67 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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
+ 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 @@
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) {
@@ -494,9 +649,8 @@
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() {
@@ -506,7 +660,6 @@
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
- mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
View expandedView = mExpandedViewContainer.getExpandedView();
if (expandedView instanceof ActivityView) {
if (expandedView.isAttachedToWindow()) {
@@ -521,53 +674,6 @@
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 97784b0..22cd2fc 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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
mDownX = rawX;
mDownY = rawY;
mMovedEnough = false;
+
+ if (isBubbleStack) {
+ stack.onDragStart();
+ } else {
+ stack.onBubbleDragStart((BubbleView) floatingView);
+ }
+
break;
case MotionEvent.ACTION_MOVE:
@@ -145,22 +147,23 @@
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 @@
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 e607dcd..dc94832 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.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;
@@ -280,24 +280,24 @@
}
@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 0000000..4f870f6
--- /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 0000000..4e0abc8
--- /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 0000000..1ced3a4
--- /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 0000000..0f51376
--- /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 7b18fad..f5ac0d3 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 @@
}
}
+ 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 @@
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 @@
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 0000000..0e49b5f
--- /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 0000000..3775515
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Layout which uses nested LinearLayouts to create a grid with the following behavior:
+ *
+ * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item
+ * count.
+ * * Display and hide sub-lists as needed, depending on the expected item count.
+ * * Favor bias toward having more rows or columns depending on the orientation of the device
+ * (TODO(123344999): Implement this, currently always favors adding more rows.)
+ * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows
+ * or columns depending on the orientation of the device.
+ * (TODO(123344999): Implement this, currently always columns.)
+ *
+ * While we could implement this behavior with a GridLayout, it would take significantly more
+ * time and effort, and would require more substantial refactoring of the existing code in
+ * GlobalActionsDialog, since it would require manipulation of the child items themselves.
+ *
+ */
+
+public class ListGridLayout extends LinearLayout {
+ private int mExpectedCount;
+ private int mRows;
+ private int mColumns;
+
+ public ListGridLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Remove all items from this grid.
+ */
+ public void removeAllItems() {
+ for (int i = 0; i < getChildCount(); i++) {
+ ViewGroup subList = (ViewGroup) getChildAt(i);
+ if (subList != null) {
+ subList.removeAllViews();
+ }
+ }
+ }
+
+ /**
+ * Get the parent view associated with the item which should be placed at the given position.
+ */
+ public ViewGroup getParentView(int index) {
+ ViewGroup firstParent = (ViewGroup) getChildAt(0);
+ if (mRows == 0) {
+ return firstParent;
+ }
+ int column = (int) Math.floor(index / mRows);
+ ViewGroup parent = (ViewGroup) getChildAt(column);
+ return parent != null ? parent : firstParent;
+ }
+
+ /**
+ * Sets the expected number of items that this grid will be responsible for rendering.
+ */
+ public void setExpectedCount(int count) {
+ mExpectedCount = count;
+ mRows = getRowCount();
+ mColumns = getColumnCount();
+
+ for (int i = 0; i < getChildCount(); i++) {
+ if (i <= mColumns) {
+ setSublistVisibility(i, true);
+ } else {
+ setSublistVisibility(i, false);
+ }
+ }
+
+ }
+
+ private void setSublistVisibility(int index, boolean visible) {
+ View subList = getChildAt(index);
+ Log.d("ListGrid", "index: " + index + ", visibility: " + visible);
+ if (subList != null) {
+ subList.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private int getRowCount() {
+ return (int) Math.ceil(Math.sqrt(mExpectedCount));
+ }
+
+ private int getColumnCount() {
+ return (int) Math.round(Math.sqrt(mExpectedCount));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 26c6d50..db5c244 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.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 @@
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 @@
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 @@
})
}
builder.setView(getContentView())
- return builder.create()
+ val dialog = builder.create()
+ dismissDialog = dialog::dismiss
+ return dialog
}
fun getContentView(): View {
@@ -116,6 +120,7 @@
return contentView
}
+ @Suppress("DEPRECATION")
private fun addAppItem(
itemList: LinearLayout,
app: PrivacyApplication,
@@ -152,6 +157,16 @@
} 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 9252167..dbe87d1 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 @@
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 2339fae..f1c3bf2 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 @@
@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 @@
else -> return null
}
if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
- val app = PrivacyApplication(appOpItem.packageName, context)
+ val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
return PrivacyItem(type, app)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 904478e..0150852 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 @@
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 @@
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 @@
}
}
+ @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 @@
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 9740d1d..e11ec2d 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 @@
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 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 @@
/** 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 @@
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 @@
}
@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 35b7ef4..5e52419 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.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 @@
}
@Override
+ public void onEntryReinflated(NotificationEntry entry) {
+ mExpansionStateLogger.onEntryReinflated(entry.key);
+ }
+
+ @Override
public void onInflationError(
StatusBarNotification notification,
Exception exception) {
@@ -468,6 +472,13 @@
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/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index c4f027f..07c6587 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 @@
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 @@
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 60a20cf..e802757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -61,7 +61,6 @@
private IActivityManager mActivityManager;
@Mock
private DozeParameters mDozeParameters;
- @Mock
private FrameLayout mStatusBarView;
@Captor
private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -80,6 +79,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mStatusBarView = new FrameLayout(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
// Bubbles get added to status bar window view
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
new file mode 100644
index 0000000..1bb7ef4
--- /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 0000000..bfc02d9
--- /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 0000000..186a762
--- /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 0000000..db819d5
--- /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 d3b3dae..f163b88 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 @@
@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 @@
@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 5ff9d14..f1d9003 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.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 @@
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 73f3b43..6177344 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 @@
@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/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index 4a8706e..2f7929c 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -55,11 +55,11 @@
"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 8adc416..84577f1 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.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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
/**
* 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;
+ }
+ }
+ }
+
+ // Execute action with least user impact
+ if (currentObserverToNotify != null) {
+ currentObserverToNotify.execute(packageToReport);
}
}
- // 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;
- }
- }
- }
+ });
}
// TODO(zezeozue): Optimize write? Maybe only write a separate smaller file?
@@ -278,21 +281,46 @@
/** 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 @@
}
private void saveToFileAsync() {
- mIoHandler.removeCallbacks(this::saveToFile);
- mIoHandler.post(this::saveToFile);
+ mWorkerHandler.removeCallbacks(this::saveToFile);
+ mWorkerHandler.post(this::saveToFile);
}
/**
@@ -606,7 +634,11 @@
} 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 d09823e..a5a515f 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.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.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 static final String EXTRA_SERVICE_VERSION = "serviceVersion";
public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
- /**
- * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
- */
+
+ /** Function to run on binder interface. */
public interface BinderRunner {
- /**
- * Runs on the retrieved binder.
- *
- * @param binder the binder retrieved from the {@link ServiceWatcher}.
- */
- void run(IBinder binder);
+ /** Called to run client code with the binder. */
+ void run(IBinder binder) throws RemoteException;
+ }
+
+ /**
+ * Function to run on binder interface.
+ * @param <T> Type to return.
+ */
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index ea6d435..f6e698f 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.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 @@
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 @@
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 @@
@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 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 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 @@
vibratorOff();
mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+ mSupportsExternalControl = vibratorSupportsExternalControl();
mContext = context;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -379,6 +382,8 @@
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 @@
}
}
+
+ // 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 @@
mThread.cancel();
mThread = null;
}
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.mute();
+ mCurrentExternalVibration = null;
+ setVibratorUnderExternalControl(false);
+ }
doVibratorOff();
reportFinishVibrationLocked();
} finally {
@@ -1095,6 +1115,26 @@
}
}
+ 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 @@
}
}
+ 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 @@
} 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 @@
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 a96676e..5d6c2f0 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 @@
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 @@
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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a376e7a..0890032 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 @@
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.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 @@
return new HealthStatsParceler(uidWriter);
}
+ /**
+ * Delay for sending ACTION_CHARGING after device is plugged in.
+ *
+ * @hide
+ */
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null);
+ final long ident = Binder.clearCallingIdentity();
+
+ try {
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ return Settings.Global.putLong(contentResolver,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+ delayMillis);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 353749f..cdf6e0e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -776,6 +776,7 @@
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7ede6dc..26d2d17 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.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 @@
}
}
+ /**
+ * 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 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 27edbbf..b71a751 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -174,10 +174,11 @@
@Override
public void onSuccess(int requestCode, int result, long timestamp) {
callback.onSuccess(requestCode, result, timestamp);
- userState.mAttentionCheckCache = new AttentionCheckCache(
- SystemClock.uptimeMillis(), result,
- timestamp);
-
+ synchronized (mLock) {
+ userState.mAttentionCheckCache = new AttentionCheckCache(
+ SystemClock.uptimeMillis(), result,
+ timestamp);
+ }
StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
result);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
new file mode 100644
index 0000000..d652f93
--- /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 0000000..eb76e6e
--- /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 de389bc..df33bf2 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_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.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.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.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.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.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.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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
@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 @@
sSoundEffectVolumeDb = context.getResources().getInteger(
com.android.internal.R.integer.config_soundEffectVolumeDb);
- mForcedUseForComm = AudioSystem.FORCE_NONE;
-
createAudioSystemThread();
AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -886,6 +711,8 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
+ ", 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 @@
+ "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 @@
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 @@
+ " 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 @@
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 @@
}
}
+ /*package*/ void setUpdateRingerModeServiceInt() {
+ setRingerModeInt(getRingerModeInternal(), false);
+ }
+
/** @see AudioManager#shouldVibrate(int) */
public boolean shouldVibrate(int vibrateType) {
if (!mHasVibrator) return false;
@@ -2921,7 +2747,7 @@
}
- 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 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 @@
// 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 @@
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
+ synchronized (mDeviceBroker.mSetModeLock) {
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
@@ -3006,11 +2830,11 @@
// 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 @@
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 @@
// 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 @@
final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
- setBluetoothScoOnInt(on, eventSource);
+
+ mDeviceBroker.setBluetoothScoOn(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);
- }
-
- /** @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 @@
!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);
+ final String eventSource = new StringBuilder("stopBluetoothSco()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
}
- 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));
- }
- }
- }
- }
-
- 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 @@
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 @@
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 @@
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 @@
|| adjust == AudioManager.ADJUST_TOGGLE_MUTE;
}
- private boolean isInCommunication() {
+ /*package*/ boolean isInCommunication() {
boolean IsInCall = false;
TelecomManager telecomManager =
@@ -4671,25 +3809,9 @@
} 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 @@
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 @@
}
}
- 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;
+ 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 setBluetoothA2dpDeviceConnectionState(
- BluetoothDevice device, int state, int profile)
- {
- return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- device, state, profile, false /* suppressNoisyIntent */,
- -1 /* a2dpVolume */);
+ /**
+ * 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");
+ }
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ + " (dis)connection, got " + state);
+ }
+ return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
+ profile, suppressNoisyIntent, a2dpVolume);
}
- 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);
- }
- 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);
- }
- return delay;
- }
-
+ /**
+ * 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 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 @@
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 @@
// 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 @@
}
// 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 @@
} 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 @@
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 @@
} 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 @@
} else {
index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(
+ AudioSystem.setStreamVolumeIndexAS(
mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
}
}
@@ -5373,6 +4399,7 @@
}
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 @@
|| (((device & mFixedVolumeDevices) != 0) && index != 0)) {
mIndexMap.put(device, mIndexMax);
}
- applyDeviceVolume_syncVSS(device);
+ applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
}
}
}
@@ -5465,11 +4492,13 @@
}
}
- 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 @@
// 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 @@
}
}
- 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 @@
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 @@
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 @@
onPersistSafeVolumeState(msg.arg1);
break;
- case MSG_BROADCAST_BT_CONNECTION_STATE:
- onBroadcastScoConnectionState(msg.arg1);
- break;
-
case MSG_SYSTEM_READY:
onSystemReady();
break;
@@ -6033,20 +4993,7 @@
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 @@
}
}
- // 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 @@
}
}
- 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);
- }
- }
+ /*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);
}
}
- 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);
- }
- }
- }
- }
- }
- }
-
- /* 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 @@
}
// 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 @@
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 @@
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 @@
// 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 @@
}
}
- // 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 @@
@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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
}
//======================
+ // 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 9d9e35b..7ccb45e 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 @@
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 @@
}
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 @@
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 @@
.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 0000000..bf32501
--- /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 dc564ba..3a25d98 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 @@
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 @@
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 41fedc5..15d66e6 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.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 @@
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 @@
}
}
+ // 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 @@
});
}
+ @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 3db6a74..fcf821f2 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.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 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 @@
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 cb3f91b..b89768a 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.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.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 @@
// 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 @@
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 @@
return mMinimumBrightnessCurve;
}
+ int getPreferredWideGamutColorSpaceIdInternal() {
+ return mWideColorSpace.getId();
+ }
+
private void setBrightnessConfigurationForUserInternal(
@Nullable BrightnessConfiguration c, @UserIdInt int userId,
@Nullable String packageName) {
@@ -2128,6 +2136,16 @@
}
}
+ @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/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index cfc85da..4349b4a 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.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
@@ -756,6 +757,14 @@
*/
private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
+ private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+ }
+
+ private static final UserManagerInternal sUserManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
private boolean mCopyOnWrite = false;
@NonNull
private String mEnabledInputMethodsStrCache = "";
@@ -833,7 +842,9 @@
if (mCopyOnWrite) {
mCopyOnWriteDataStore.put(key, str);
} else {
- Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+ final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+ Settings.Secure.putStringForUser(mResolver, key, str, userId);
}
}
@@ -852,14 +863,16 @@
if (mCopyOnWrite) {
mCopyOnWriteDataStore.put(key, String.valueOf(value));
} else {
- Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
+ final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+ Settings.Secure.putIntForUser(mResolver, key, value, userId);
}
}
private int getInt(String key, int defaultValue) {
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
final String result = mCopyOnWriteDataStore.get(key);
- return result != null ? Integer.parseInt(result) : 0;
+ return result != null ? Integer.parseInt(result) : defaultValue;
}
return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
}
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index f1de371..e6f0ed9 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.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 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 a6da8c5..6b5b1be 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 @@
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 @@
}
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 92b4d5f..c2c95e6 100644
--- a/services/core/java/com/android/server/location/OWNERS
+++ b/services/core/java/com/android/server/location/OWNERS
@@ -1,6 +1,8 @@
+aadmal@google.com
arthuri@google.com
bduddie@google.com
gomo@google.com
sooniln@google.com
weiwa@google.com
wyattriley@google.com
+yuhany@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a164686..de3f50a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1087,7 +1087,8 @@
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
- || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+ || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
+ || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_ALL);
String pkgList[] = null;
@@ -1108,6 +1109,23 @@
uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
cancelNotifications = false;
unhideNotifications = true;
+ } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
+ final int distractionRestrictions =
+ intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS,
+ PackageManager.RESTRICTION_NONE);
+ if ((distractionRestrictions
+ & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ hideNotifications = true;
+ } else {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ unhideNotifications = true;
+ }
+
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1651,6 +1669,7 @@
IntentFilter suspendedPkgFilter = new IntentFilter();
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
suspendedPkgFilter, null, null);
@@ -7743,6 +7762,20 @@
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 02fc51f..ab49ebb 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 @@
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 @@
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 3d88f20..2aaa1ed 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 @@
// only use for testing
mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
}
+ case "distract_package": {
+ // only use for testing
+ // Flag values are in
+ // {@link android.content.pm.PackageManager.DistractionRestriction}.
+ mDirectService.simulatePackageDistractionBroadcast(
+ Integer.parseInt(getNextArgRequired()),
+ getNextArgRequired().split(","));
+ }
break;
case "post":
case "notify":
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eab5c8f..146a2f3 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.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 @@
private final Callbacks mCallbacks;
+ private volatile boolean mBootCompleted = false;
+
/**
* File storing persisted {@link #mSessions} metadata.
*/
@@ -203,9 +207,20 @@
mStagingManager = new StagingManager(pm);
}
+ private void setBootCompleted() {
+ mBootCompleted = true;
+ }
+
public void systemReady() {
mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ setBootCompleted();
+ mContext.unregisterReceiver(this);
+ }
+ }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
synchronized (mSessions) {
readSessionsLocked();
@@ -1126,8 +1141,10 @@
public void onStagedSessionChanged(PackageInstallerSession session) {
writeSessionsAsync();
- // TODO(b/118865310): don't send broadcast if system is not ready.
- mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+ if (mBootCompleted) {
+ mPm.sendSessionUpdatedBroadcast(session.generateInfo(false),
+ session.userId);
+ }
}
public void onSessionFinished(final PackageInstallerSession session, boolean success) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 692c032..6f1eeeb 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.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 @@
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 @@
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 b930d26..c4d27e5 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 @@
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;
}
@@ -202,11 +215,18 @@
+ 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;
}
}
}
+
session.setStagedSessionReady();
+ if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
+ session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+ "APEX staging failed, check logcat messages from apexd for more "
+ + "details.");
+ }
}
private void resumeSession(@NonNull PackageInstallerSession session) {
@@ -227,11 +247,16 @@
"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 30b5e49..3c89d78 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 @@
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 @@
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 076c94c..09bacd6 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 0000000..a2c8dac
--- /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 a027873..3be6480 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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
}
mNotifier.onUserActivity(event, uid);
+ mAttentionDetector.onUserActivity(eventTime, event);
if (mUserInactiveOverrideFromWindowManager) {
mUserInactiveOverrideFromWindowManager = false;
@@ -1593,6 +1607,7 @@
if (mNotifier != null) {
mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
}
+ mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
}
}
@@ -2085,6 +2100,10 @@
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 @@
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 0000000..2ec3993
--- /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 1c7596b..7d8c7c9 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.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 @@
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 8b4c410..e59228a 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.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.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 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 1f2f1cc..fcae618 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 @@
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 com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import java.util.List;
@@ -38,10 +40,12 @@
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 @@
}
@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 @@
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 7738be9..3b24b3e 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 acede7d..c6d2870 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 @@
@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 @@
}
}
+ 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 @@
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 @@
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 a66f0ca..b9b5aae 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 @@
* @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 8d2bab4..7e87c29 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 @@
} 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 23c042a..4adce58 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 @@
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 @@
// 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 @@
}
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 69040a2..b0ef8a0 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 @@
*/
private void extractColors(WallpaperData wallpaper) {
String cropFile = null;
+ boolean defaultImageWallpaper = false;
int wallpaperId;
if (wallpaper.equals(mFallbackWallpaper)) {
@@ -482,6 +483,8 @@
|| 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 @@
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 65d66f4..e817dd4 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 @@
}
/**
- * 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8634d8..b7c35c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,30 +1946,90 @@
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");
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */));
- }
+ makeActiveIfNeeded(null /* activeActivity*/);
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
}
}
- /** 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
+ /**
+ * 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);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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 +2039,14 @@
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;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3a754c4..c97e4e8 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 @@
*/
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 @@
"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 @@
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 @@
if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
+
+ if (notifyClients) {
+ r.makeActiveIfNeeded(starting);
+ }
} else {
r.makeVisibleIfNeeded(starting, notifyClients);
}
@@ -2327,7 +2336,7 @@
r.setVisible(true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
+ mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
return true;
}
}
@@ -2505,7 +2514,7 @@
*/
@GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
- if (mStackSupervisor.inResumeTopActivity) {
+ if (mInResumeTopActivity) {
// Don't even start recursing.
return false;
}
@@ -2513,7 +2522,7 @@
boolean result = false;
try {
// Protect against recursion.
- mStackSupervisor.inResumeTopActivity = true;
+ mInResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
// When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2528,7 +2537,7 @@
checkReadyForSleep();
}
} finally {
- mStackSupervisor.inResumeTopActivity = false;
+ mInResumeTopActivity = false;
}
return result;
@@ -2561,7 +2570,7 @@
// 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 +2658,12 @@
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 +2873,9 @@
// 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 +4104,12 @@
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 3a288ca..a83ef34 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 @@
*/
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.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2807094..43c1206 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 @@
.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 3a077b8..b0e5811 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -827,6 +827,8 @@
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));
@@ -980,7 +982,8 @@
/** Returns true if uid is in a persistent state. */
private boolean isUidPersistentSystemProcess(int uid) {
- return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+ return (uid == Process.SYSTEM_UID)
+ || (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
}
private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid,
@@ -1630,7 +1633,7 @@
// 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 29645f6..ed029cd 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 @@
.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 4006332..bbf115f 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 @@
}
}
+ 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/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 9b72141..e95ac5c 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,28 +1108,41 @@
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 applySleepTokens(boolean applyToStacks) {
@@ -1283,16 +1296,21 @@
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);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7dd55b..476bd6e 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_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 @@
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 69dcaf4..0529ed1 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 @@
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 c2a8e7e..dea3597 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 @@
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.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 @@
.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 e99dd4f..bbecc63 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.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 @@
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 @@
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 f068c3a..1fe2328 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 a61c2ef..0000000
--- 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 2c368c8..0007350 100644
--- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -73,7 +73,7 @@
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 be0f5f2..f5ae725 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 @@
*/
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 @@
// NOTE: ServiceManager does no caching for the netd service,
// because netd is not one of the defined common services.
final INetd netdInstance = INetd.Stub.asInterface(
- ServiceManager.getService(NETD_SERVICE_NAME));
+ ServiceManager.getService(Context.NETD_SERVICE));
if (netdInstance == null) {
Log.w(TAG, "WARNING: returning null INetd instance.");
}
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
new file mode 100644
index 0000000..9f1cbcd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.attention.AttentionManagerInternal;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class AttentionDetectorTest extends AndroidTestCase {
+
+ private @Mock AttentionManagerInternal mAttentionManagerInternal;
+ private @Mock Runnable mOnUserAttention;
+ private TestableAttentionDetector mAttentionDetector;
+ private long mAttentionTimeout;
+ private long mNextDimming;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any()))
+ .thenReturn(true);
+ mAttentionDetector = new TestableAttentionDetector();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mAttentionDetector.setAttentionServiceSupported(true);
+ mNextDimming = SystemClock.uptimeMillis() + 3000L;
+ }
+
+ @Test
+ public void testOnUserActivity_checksAttention() {
+ long when = registerAttention();
+ verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnUserActivity_doesntCheckIfNotSupported() {
+ mAttentionDetector.setAttentionServiceSupported(false);
+ long when = registerAttention();
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(mNextDimming).isEqualTo(when);
+ }
+
+ @Test
+ public void onUserActivity_ignoresWhiteListedActivityTypes() {
+ for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) {
+ int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i);
+ if (result == -1) {
+ throw new AssertionError("User activity " + i + " isn't listed in"
+ + " AttentionDetector#onUserActivity. Please consider how this new activity"
+ + " type affects the attention service.");
+ }
+ }
+ }
+
+ @Test
+ public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() {
+ long now = SystemClock.uptimeMillis();
+ mNextDimming = now;
+ mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(mNextDimming + 5000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_ignoresAfterMaximumExtension() {
+ long now = SystemClock.uptimeMillis();
+ mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(now + 2000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_skipsIfAlreadyScheduled() {
+ registerAttention();
+ reset(mAttentionManagerInternal);
+ long when = mAttentionDetector.updateUserActivity(mNextDimming);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() {
+ registerAttention();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt());
+ }
+
+ @Test
+ public void testCallbackOnSuccess_ignoresIfNoAttention() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ @Test
+ public void testCallbackOnSuccess_callsCallback() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention).run();
+ }
+
+ @Test
+ public void testCallbackOnFailure_unregistersCurrentRequestCode() {
+ registerAttention();
+ mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ private long registerAttention() {
+ mAttentionTimeout = 4000L;
+ mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ return mAttentionDetector.updateUserActivity(mNextDimming);
+ }
+
+ private class TestableAttentionDetector extends AttentionDetector {
+
+ private boolean mAttentionServiceSupported;
+
+ TestableAttentionDetector() {
+ super(AttentionDetectorTest.this.mOnUserAttention, new Object());
+ mAttentionManager = mAttentionManagerInternal;
+ mMaximumExtensionMillis = 10000L;
+ }
+
+ void setAttentionServiceSupported(boolean supported) {
+ mAttentionServiceSupported = supported;
+ }
+
+ @Override
+ public boolean isAttentionServiceSupported() {
+ return mAttentionServiceSupported;
+ }
+
+ @Override
+ public long getAttentionTimeout() {
+ return mAttentionTimeout;
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 410ab87..e658d17 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 @@
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 6222923..9c6ab0a 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 @@
}
@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 8be63fc..319ffed 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_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 @@
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 @@
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/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 68df87e..ea8f33f 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.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 @@
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 @@
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
+ mLaunchingActivity = mock(PowerManager.WakeLock.class);
+
initialize();
}
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 c2e4581..acf9946 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.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 @@
@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 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 @@
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 @@
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 7fcad36..9a30b35 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.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 @@
*/
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 @@
* @param context The system server context.
*/
public IorapForwardingService(Context context) {
- super(context);
+ super(context);
}
//<editor-fold desc="Providers">
@@ -78,12 +92,40 @@
@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 @@
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 @@
// 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 @@
invokeRemote(() ->
mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+ new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
);
}
}
@@ -201,6 +358,7 @@
}
}
+ /** Allow passing lambdas to #invokeRemote */
private interface RemoteRunnable {
void run() throws RemoteException;
}
@@ -209,8 +367,26 @@
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 f83a16e..919154d 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 0000000..aa88615
--- /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 bf9bf9a..4475630 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1631,8 +1631,9 @@
/** @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 d777bf1..fae7920 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.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.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 @@
}
}
+ /** 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 e710e0e..98fc725 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6848,13 +6848,13 @@
/**
* 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 @@
/** @hide */
@SystemApi
+ @TestApi
@SuppressLint("Doclava125")
public int checkCarrierPrivilegesForPackage(String pkgName) {
try {
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 294c79b..3d2fe5f 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -23,6 +23,7 @@
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 @@
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 @@
* @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 @@
* 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 @@
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 @@
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 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 @@
.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 @@
&& 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 @@
@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 @@
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 da4822c..1d196f9 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 @@
*/
@SystemApi
public final class DataProfile implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"TYPE_"},
+ value = {
+ TYPE_COMMON,
+ TYPE_3GPP,
+ TYPE_3GPP2})
+ public @interface DataProfileType {}
- // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network.
+ /** 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 @@
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 @@
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 @@
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 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 @@
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 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 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 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 @@
@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 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 @@
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 0000000..ac4d17a
--- /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 5b2e635..eb99d5d 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -54,7 +54,7 @@
* 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 @@
/**
* 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 @@
}
/**
- * 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 @@
/**
* 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 @@
* @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 @@
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 @@
/**
* 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 @@
* @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 @@
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 @@
}
}
- 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 086a765..b171f79 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -172,15 +172,13 @@
/**
* 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 @@
* @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 @@
* @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 @@
* 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 f35e886..fea763e 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -21,8 +21,10 @@
/**
* 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 a4eb424..b51eda3 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -18,6 +18,8 @@
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 @@
* @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 1cdf44d..12c5c30 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -18,6 +18,8 @@
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 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 9300034..d9b206f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -268,31 +268,11 @@
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 576f24a..f69db2c 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.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 @@
}
@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/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index 979d13a..ee02a72 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -35,7 +35,6 @@
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 @@
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 @@
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 d68769b..e92cc56 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 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 @@
String expectedNameHash =
"996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -170,7 +171,7 @@
String expectedNameHash =
"8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -307,6 +308,12 @@
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 07c4a93..c16efbd 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 @@
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 @@
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 @@
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 89c6aed..5723c59 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 @@
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 7870902..7692836 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.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 @@
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 6801fd9..694e0d9 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 270adf8..5c240589 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 07c453c..fccc7b9 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 ec07037..86af642 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.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 @@
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 @@
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 @@
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_A and APP_B above the threshold
+ // Then fail APP_C (not observed) 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_C});
}
- // 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 that observers are not notified
+ assertEquals(0, observer1.mFailedPackages.size());
+ assertEquals(0, observer2.mFailedPackages.size());
}
/**
- * Test package failure and notifies only one observer because it handles the failure
+ * Test package failure and notifies only least impact observers.
*/
@Test
- public void testPackageFailureNotifyOne() throws Exception {
+ public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
PackageWatchdog watchdog = createWatchdog();
- TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
- TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
+ 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, APP_C, APP_D});
+ }
+
+ // 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 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 testPackageFailureNotifyLeastSuccessively() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ 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 @@
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 @@
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 boolean onHealthCheckFailed(String packageName) {
+ public int onHealthCheckFailed(String packageName) {
+ return mImpact;
+ }
+
+ 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 030641b..e10f866 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 @@
/**
* 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 @@
*/
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 13ac4f0..0493ef8 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.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.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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}
/**
- * 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 @@
// 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 @@
// 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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
RollbackManager rm = RollbackTestUtils.getRollbackManager();
try {
- rm.getAvailableRollback(TEST_APP_A);
+ rm.getAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
- rm.getPackagesWithAvailableRollbacks();
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.getRecentlyExecutedRollbacks();
+ rm.getRecentlyCommittedRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -581,7 +544,7 @@
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 @@
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 @@
// 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 @@
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 edb1355..1478657 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.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 @@
}
/**
- * 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 3452819..ba6e0f2 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 @@
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 5bb5734..2b5ad37 100644
--- a/tests/net/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/java/android/net/StaticIpConfigurationTest.java
@@ -26,13 +26,13 @@
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 @@
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 80aac04..f7542a7 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.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 0000000..6da8514
--- /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 14df392..fb4d43c 100644
--- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -62,7 +62,7 @@
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 7783e10..8f75287 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -182,7 +182,8 @@
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 85f9080..7a74ba9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -284,6 +284,8 @@
// 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 @@
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 8da5bb8..3c0fe37 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 @@
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
new file mode 100644
index 0000000..c558a33
--- /dev/null
+++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
Binary files differ
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
index 961b47c..cee0cd5 100644
--- a/tools/bit/aapt.cpp
+++ b/tools/bit/aapt.cpp
@@ -159,10 +159,11 @@
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 @@
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 a71cea1..860094ae 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -623,12 +623,13 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 5a9ab22..6270913 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 @@
}
}
-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 1c9504d..db0b69f 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -31,16 +31,26 @@
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 790e0b4..35feda1 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -116,6 +116,20 @@
}
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 b6c3e9a..db6cf5f 100644
--- a/tools/bit/print.h
+++ b/tools/bit/print.h
@@ -33,6 +33,7 @@
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/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 35fba3d..488de87 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 @@
@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 @@
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 @@
setMeteredHint(false);
setEphemeral(false);
setOsuAp(false);
+ setNetworkSuggestionOrSpecifierPackageName(null);
txBad = 0;
txSuccess = 0;
rxSuccess = 0;
@@ -240,6 +254,8 @@
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 @@
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 @@
dest.writeDouble(rxSuccessRate);
mSupplicantState.writeToParcel(dest, flags);
dest.writeInt(mOsuAp ? 1 : 0);
+ dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
}
/** Implement the Parcelable interface {@hide} */
@@ -672,6 +700,7 @@
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 8086039..0668239 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1404,7 +1404,6 @@
* {@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 +1427,6 @@
* 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 +1603,6 @@
* 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 +1632,6 @@
* @param callback Callback for network match events
* @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void unregisterNetworkRequestMatchCallback(
@NonNull NetworkRequestMatchCallback callback) {
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 677bf37..948dcfa 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -35,6 +35,7 @@
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 @@
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 @@
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
assertTrue(readWifiInfo.isTrusted());
assertTrue(readWifiInfo.isOsuAp());
+ assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
}
}