diff options
103 files changed, 5246 insertions, 2962 deletions
diff --git a/Android.bp b/Android.bp index a5cc89cd0ea1..d4a04cc43fe7 100644 --- a/Android.bp +++ b/Android.bp @@ -726,7 +726,7 @@ java_defaults { "ext", ], - jarjar_rules: ":framework-hidl-jarjar", + jarjar_rules: "jarjar_rules_hidl.txt", static_libs: [ "apex_aidl_interface-java", @@ -738,6 +738,15 @@ java_defaults { "android.hardware.cas-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.0-java", + "android.hardware.radio-V1.1-java", + "android.hardware.radio-V1.2-java", + "android.hardware.radio-V1.3-java", + "android.hardware.radio-V1.4-java", + "android.hardware.radio.config-V1.0-java", + "android.hardware.radio.config-V1.1-java", + "android.hardware.radio.config-V1.2-java", + "android.hardware.radio.deprecated-V1.0-java", "android.hardware.thermal-V1.0-java-constants", "android.hardware.thermal-V1.0-java", "android.hardware.thermal-V1.1-java", @@ -746,15 +755,12 @@ java_defaults { "android.hardware.usb-V1.0-java-constants", "android.hardware.usb-V1.1-java-constants", "android.hardware.usb-V1.2-java-constants", + "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", "android.hardware.wifi-V1.0-java-constants", - "android.hardware.radio-V1.0-java", - "android.hardware.radio-V1.3-java", - "android.hardware.radio-V1.4-java", - "android.hardware.usb.gadget-V1.0-java", "networkstack-aidl-interfaces-java", "netd_aidl_interface-java", "devicepolicyprotosnano", @@ -788,11 +794,6 @@ filegroup { ], } -filegroup { - name: "framework-hidl-jarjar", - srcs: ["jarjar_rules_hidl.txt"], -} - java_library { name: "framework", defaults: ["framework-defaults"], diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java index a7a81f2d20bb..767434d0831c 100644 --- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java +++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java @@ -91,7 +91,7 @@ public class TextClassifierPerfTest { private static ConversationActions.Request createConversationActionsRequest(CharSequence text) { ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(text) .build(); return new ConversationActions.Request.Builder(Collections.singletonList(message)) diff --git a/api/current.txt b/api/current.txt index 08b0e0385b8c..bb6dbeba2e7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5466,9 +5466,11 @@ package android.app { public static final class Notification.BubbleMetadata implements android.os.Parcelable { method public int describeContents(); + method public boolean getAutoExpandBubble(); method public int getDesiredHeight(); method public android.graphics.drawable.Icon getIcon(); method public android.app.PendingIntent getIntent(); + method public boolean getSuppressInitialNotification(); method public CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR; @@ -5477,9 +5479,11 @@ package android.app { public static class Notification.BubbleMetadata.Builder { ctor public Notification.BubbleMetadata.Builder(); method public android.app.Notification.BubbleMetadata build(); + method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean); method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int); method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon); method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent); + method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean); method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence); } @@ -38745,6 +38749,7 @@ package android.provider { public static final class Settings.Panel { field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final String ACTION_NFC = "android.settings.panel.action.NFC"; field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; } @@ -53341,8 +53346,8 @@ package android.view.textclassifier { method @Nullable public CharSequence getText(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; - field public static final android.app.Person PERSON_USER_LOCAL; - field public static final android.app.Person PERSON_USER_REMOTE; + field public static final android.app.Person PERSON_USER_OTHERS; + field public static final android.app.Person PERSON_USER_SELF; } public static final class ConversationActions.Message.Builder { @@ -62381,20 +62386,20 @@ package java.nio { method public abstract Object array(); method public abstract int arrayOffset(); method public final int capacity(); - method public final java.nio.Buffer clear(); - method public final java.nio.Buffer flip(); + method public java.nio.Buffer clear(); + method public java.nio.Buffer flip(); method public abstract boolean hasArray(); method public final boolean hasRemaining(); method public abstract boolean isDirect(); method public abstract boolean isReadOnly(); method public final int limit(); - method public final java.nio.Buffer limit(int); - method public final java.nio.Buffer mark(); + method public java.nio.Buffer limit(int); + method public java.nio.Buffer mark(); method public final int position(); - method public final java.nio.Buffer position(int); + method public java.nio.Buffer position(int); method public final int remaining(); - method public final java.nio.Buffer reset(); - method public final java.nio.Buffer rewind(); + method public java.nio.Buffer reset(); + method public java.nio.Buffer rewind(); } public class BufferOverflowException extends java.lang.RuntimeException { diff --git a/api/system-current.txt b/api/system-current.txt index b0360cf02653..be07151a5cc8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4694,7 +4694,6 @@ package android.net.wifi { method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); @@ -4704,7 +4703,6 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int); field public static final int CHANGE_REASON_ADDED = 0; // 0x0 field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2 @@ -4741,19 +4739,6 @@ package android.net.wifi { method public void onSuccess(); } - public static interface WifiManager.NetworkRequestMatchCallback { - method public void onAbort(); - method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>); - method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback); - method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration); - method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration); - } - - public static interface WifiManager.NetworkRequestUserSelectionCallback { - method public void reject(); - method public void select(@NonNull android.net.wifi.WifiConfiguration); - } - public static interface WifiManager.WifiUsabilityStatsListener { method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry); } @@ -5057,6 +5042,7 @@ package android.nfc { package android.os { public class BatteryManager { + method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int); field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS"; field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; } @@ -8258,6 +8244,16 @@ package android.telephony.ims { field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants; } + public class ImsException extends java.lang.Exception { + ctor public ImsException(@Nullable String); + ctor public ImsException(@Nullable String, int); + ctor public ImsException(@Nullable String, int, Throwable); + method public int getCode(); + field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1 + field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2 + } + public final class ImsExternalCallState implements android.os.Parcelable { ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean); method public int describeContents(); @@ -8275,7 +8271,7 @@ package android.telephony.ims { } public class ImsMmTelManager { - method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled(); @@ -8284,8 +8280,8 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int); @@ -8720,13 +8716,13 @@ package android.telephony.ims { } public class ProvisioningManager { - method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); + method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); + method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index d6b4737f5bbd..1a0a98352051 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -212,7 +212,7 @@ message Atom { } // 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; @@ -260,6 +260,8 @@ message Atom { BatteryLevel battery_level = 10043; BuildInformation build_information = 10044; BatteryCycleCount battery_cycle_count = 10045; + DebugElapsedClock debug_elapsed_clock = 10046; + DebugFailingElapsedClock debug_failing_elapsed_clock = 10047; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -4557,3 +4559,39 @@ message UsbContaminantReported { optional string id = 1; optional android.service.usb.ContaminantPresenceStatus status = 2; } + +/** + * This atom is for debugging purpose. + */ +message DebugElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; + + enum Type { + TYPE_UNKNOWN = 0; + ALWAYS_PRESENT = 1; + PRESENT_ON_ODD_PULLS = 2; + } + // Type of behavior for the pulled data. + optional Type type = 5; +} + +/** + * This atom is for debugging purpose. + */ +message DebugFailingElapsedClock { + // Monotically increasing value for each pull. + optional int64 pull_count = 1; + // Time from System.elapsedRealtime. + optional int64 elapsed_clock_millis = 2; + // Time from System.elapsedRealtime. + optional int64 same_elapsed_clock_millis = 3; + // Diff between current elapsed time and elapsed time from previous pull. + optional int64 elapsed_clock_diff_millis = 4; +} diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 7e56beeefbf6..4585a09176ce 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -209,6 +209,14 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER, {.puller = new StatsCompanionServicePuller( android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, + // DebugElapsedClock. + {android::util::DEBUG_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}}, + // DebugFailingElapsedClock. + {android::util::DEBUG_FAILING_ELAPSED_CLOCK, + {.additiveFields = {1, 2, 3, 4}, + .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, // BuildInformation. {android::util::BUILD_INFORMATION, {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b405d0ccc3d1..7c550d4332fe 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -84,7 +84,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -8403,6 +8402,30 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * in its expanded state, with the contents of {@link #getIntent()} in a floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * <b>without</b> the associated notification in the notification shade. Subsequent update + * notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002; private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) { mPendingIntent = intent; @@ -8416,6 +8439,7 @@ public class Notification implements Parcelable mTitle = in.readCharSequence(); mIcon = Icon.CREATOR.createFromParcel(in); mDesiredHeight = in.readInt(); + mFlags = in.readInt(); } /** @@ -8448,6 +8472,24 @@ public class Notification implements Parcelable return mDesiredHeight; } + /** + * @return whether this bubble should auto expand when it is posted. + * + * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) + */ + public boolean getAutoExpandBubble() { + return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; + } + + /** + * @return whether this bubble should suppress the initial notification when it is posted. + * + * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean) + */ + public boolean getSuppressInitialNotification() { + return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0; + } + public static final Parcelable.Creator<BubbleMetadata> CREATOR = new Parcelable.Creator<BubbleMetadata>() { @@ -8473,6 +8515,11 @@ public class Notification implements Parcelable out.writeCharSequence(mTitle); mIcon.writeToParcel(out, 0); out.writeInt(mDesiredHeight); + out.writeInt(mFlags); + } + + private void setFlags(int flags) { + mFlags = flags; } /** @@ -8484,6 +8531,7 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; /** * Constructs a new builder object. @@ -8543,6 +8591,39 @@ public class Notification implements Parcelable } /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted in its expanded state, with the contents of {@link #getIntent()} in a + * floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { + setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); + return this; + } + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted <b>without</b> the associated notification in the notification shade. + * Subsequent update notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setSuppressInitialNotification( + boolean shouldSupressNotif) { + setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif); + return this; + } + + /** * Creates the {@link BubbleMetadata} defined by this builder. * <p>Will throw {@link IllegalStateException} if required fields have not been set * on this builder.</p> @@ -8557,7 +8638,22 @@ public class Notification implements Parcelable if (mIcon == null) { throw new IllegalStateException("Must supply an icon for the bubble"); } - return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight); + BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon, + mDesiredHeight); + data.setFlags(mFlags); + return data; + } + + /** + * @hide + */ + public BubbleMetadata.Builder setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + return this; } } } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 171c2f5b1a08..c4bf1ebd1a6f 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -434,7 +434,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override - public int getConnectionState(BluetoothDevice device) { + public @BtProfileState int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); try { mServiceLock.readLock().lock(); @@ -689,7 +689,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage - public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); try { mServiceLock.readLock().lock(); diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java index 78560d2de420..2cb7b2d3c7e9 100644 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ b/core/java/android/bluetooth/BluetoothCodecStatus.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -42,7 +43,7 @@ public final class BluetoothCodecStatus implements Parcelable { public static final String EXTRA_CODEC_STATUS = "android.bluetooth.codec.extra.CODEC_STATUS"; - private final BluetoothCodecConfig mCodecConfig; + private final @Nullable BluetoothCodecConfig mCodecConfig; private final BluetoothCodecConfig[] mCodecsLocalCapabilities; private final BluetoothCodecConfig[] mCodecsSelectableCapabilities; @@ -140,7 +141,7 @@ public final class BluetoothCodecStatus implements Parcelable { * @return the current codec configuration */ @UnsupportedAppUsage - public BluetoothCodecConfig getCodecConfig() { + public @Nullable BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index b8670dbeadad..ef775967f8a7 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -18,11 +18,14 @@ package android.bluetooth; import android.Manifest; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -60,6 +63,16 @@ public interface BluetoothProfile { /** The profile is in disconnecting state */ int STATE_DISCONNECTING = 3; + /** @hide */ + @IntDef({ + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileState {} + /** * Headset and Handsfree profile */ diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 6704a45fb07a..47a4a2dca73d 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -87,6 +87,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * <p>For more information about using a ContentResolver with content providers, read the * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> * developer guide.</p> + * </div> */ public abstract class ContentResolver implements ContentInterface { /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index edd765b05415..9e7aaf652a4d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -9976,9 +9976,21 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ + public void writeToProto(ProtoOutputStream proto) { + // Same input parameters that toString() gives to toShortString(). + writeToProtoWithoutFieldId(proto, true, true, true, false); + } + + /** @hide */ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp, boolean extras, boolean clip) { long token = proto.start(fieldId); + writeToProtoWithoutFieldId(proto, secure, comp, extras, clip); + proto.end(token); + } + + private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp, + boolean extras, boolean clip) { if (mAction != null) { proto.write(IntentProto.ACTION, mAction); } @@ -10023,7 +10035,6 @@ public class Intent implements Parcelable, Cloneable { if (mSelector != null) { proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip)); } - proto.end(token); } /** diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 9d37d9939127..209afb88ccd3 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -252,12 +252,55 @@ public interface BiometricFaceConstants { public static final int FACE_ACQUIRED_TOO_SIMILAR = 15; /** + * The magnitude of the pan angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The pan angle is defined as the angle swept out by the user’s face turning + * their neck left and right. The pan angle would be zero if the user faced the + * camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; + + /** + * The magnitude of the tilt angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The tilt angle is defined as the angle swept out by the user’s face looking up + * and down. The pan angle would be zero if the user faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; + + /** + * The magnitude of the roll angle of the user’s face with respect to the sensor’s + * capture plane is too high. + * + * The roll angle is defined as the angle swept out by the user’s face tilting their head + * towards their shoulders to the left and right. The pan angle would be zero if the user + * faced the camera directly. + * + * The user should be informed to look more directly at the camera. + */ + public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; + + /** + * The user’s face has been obscured by some object. + * + * The user should be informed to remove any objects from the line of sight from + * the sensor to the user’s face. + */ + public static final int FACE_ACQUIRED_FACE_OBSCURED = 19; + + /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. * * @hide */ - public static final int FACE_ACQUIRED_VENDOR = 16; + public static final int FACE_ACQUIRED_VENDOR = 20; /** * @hide diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 954071a0ee97..3fc5e41ad990 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,6 +16,8 @@ package android.os; +import android.Manifest.permission; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; @@ -369,4 +371,26 @@ public class BatteryManager { throw e.rethrowFromSystemServer(); } } + + /** + * Sets the delay for reporting battery state as charging after device is plugged in. + * This allows machine-learning or heuristics to delay the reporting and the corresponding + * broadcast, based on battery level, charging rate, and/or other parameters. + * + * @param delayMillis the delay in milliseconds, negative value to reset. + * + * @return True if the delay was set successfully. + * + * @see ACTION_CHARGING + * @hide + */ + @RequiresPermission(permission.POWER_SAVER) + @SystemApi + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + try { + return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index eae1aa5eb750..1919fcc00a33 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -834,10 +834,10 @@ public abstract class BatteryStats implements Parcelable { * also be bumped. */ static final String[] USER_ACTIVITY_TYPES = { - "other", "button", "touch", "accessibility" + "other", "button", "touch", "accessibility", "attention" }; - public static final int NUM_USER_ACTIVITY_TYPES = 4; + public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index d463b4422847..6f5f8072f471 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. @@ -34,7 +36,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ // TODO: Expose API when the implementation is more complete. -// @SystemApi +//@SystemApi @SystemService(Context.BUGREPORT_SERVICE) public class BugreportManager { private final Context mContext; @@ -47,47 +49,46 @@ public class BugreportManager { } /** - * An interface describing the listener for bugreport progress and status. + * An interface describing the callback for bugreport progress and status. */ - public interface BugreportListener { - /** - * Called when there is a progress update. - * @param progress the progress in [0.0, 100.0] - */ - void onProgress(float progress); - + public abstract static class BugreportCallback { + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, - BUGREPORT_ERROR_RUNTIME + BUGREPORT_ERROR_RUNTIME, + BUGREPORT_ERROR_USER_DENIED_CONSENT, + BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ - @interface BugreportErrorCode {} + public @interface BugreportErrorCode {} /** The input options were invalid */ - int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; + public static final int BUGREPORT_ERROR_INVALID_INPUT = + IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ - int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; + public static final int BUGREPORT_ERROR_RUNTIME = + IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ - int BUGREPORT_ERROR_USER_DENIED_CONSENT = + public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; /** The request to get user consent timed out. */ - int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = + public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + public void onProgress(float progress) {} + + /** * Called when taking bugreport resulted in an error. * - * @param errorCode the error that occurred. Possible values are - * {@code BUGREPORT_ERROR_INVALID_INPUT}, - * {@code BUGREPORT_ERROR_RUNTIME}, - * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}, - * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}. - * * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not * consent to sharing the bugreport with the calling app. * @@ -95,19 +96,19 @@ public class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. */ - void onError(@BugreportErrorCode int errorCode); + public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ - void onFinished(); + public void onFinished() {} } /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several - * seconds to return in the worst case. {@code listener} will receive progress and status + * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>The bugreport artifacts will be copied over to the given file descriptors only if the @@ -118,19 +119,23 @@ public class BugreportManager { * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param params options that specify what kind of a bugreport should be taken - * @param listener callback for progress and status updates + * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) - public void startBugreport(@NonNull FileDescriptor bugreportFd, - @Nullable FileDescriptor screenshotFd, - @NonNull BugreportParams params, @NonNull BugreportListener listener) { + public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, + @Nullable ParcelFileDescriptor screenshotFd, + @NonNull BugreportParams params, + @NonNull @CallbackExecutor Executor executor, + @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. - DumpstateListener dsListener = new DumpstateListener(listener); - + DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, - mContext.getOpPackageName(), bugreportFd, screenshotFd, + mContext.getOpPackageName(), + (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), + (screenshotFd != null + ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -151,10 +156,12 @@ public class BugreportManager { private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { - private final BugreportListener mListener; + private final Executor mExecutor; + private final BugreportCallback mCallback; - DumpstateListener(@Nullable BugreportListener listener) { - mListener = listener; + DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { + mExecutor = executor; + mCallback = callback; } @Override @@ -164,19 +171,37 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { - mListener.onProgress(progress); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onProgress(progress); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onError(int errorCode) throws RemoteException { - mListener.onError(errorCode); + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCallback.onError(errorCode); + }); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void onFinished() throws RemoteException { + final long identity = Binder.clearCallingIdentity(); try { - mListener.onFinished(); + mExecutor.execute(() -> { + mCallback.onFinished(); + }); } finally { + Binder.restoreCallingIdentity(identity); // The bugreport has finished. Let's shutdown the service to minimize its footprint. cancelBugreport(); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 4ce760f2c4a6..7f4254e29aaa 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -336,6 +336,13 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; /** + * User activity event type: {@link android.service.attention.AttentionService} taking action + * on behalf of user. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_ATTENTION = 4; + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d840e3c720cc..852b65a05034 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14280,6 +14280,17 @@ public final class Settings { */ public static final String APPOP_HISTORY_PARAMETERS = "appop_history_parameters"; + + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * This is used as an override for constants defined in BatteryStatsImpl for + * ease of experimentation. + * + * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS + * @hide + */ + public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY = + "battery_charging_state_update_delay"; } /** @@ -14603,6 +14614,17 @@ public final class Settings { "android.settings.panel.action.INTERNET_CONNECTIVITY"; /** + * Activity Action: Show a settings dialog containing NFC-related settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NFC = + "android.settings.panel.action.NFC"; + + /** * Activity Action: Show a settings dialog containing all volume streams. * <p> * Input: Nothing. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 863b717008d2..4032a6b84801 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -46,10 +46,9 @@ import android.hardware.display.DisplayedContentSamplingAttributes; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import android.view.Surface.OutOfResourcesException; @@ -60,6 +59,7 @@ import dalvik.system.CloseGuard; import libcore.util.NativeAllocationRegistry; import java.io.Closeable; +import java.nio.ByteBuffer; /** * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is @@ -75,7 +75,7 @@ public final class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, - int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid) + int w, int h, int format, int flags, long parentObject, Parcel metadata) throws OutOfResourcesException; private static native long nativeReadFromParcel(Parcel in); private static native long nativeCopyFromSurfaceControl(long nativeObject); @@ -182,6 +182,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken, IBinder toToken); private static native boolean nativeGetProtectedContentSupport(); + private static native void nativeSetMetadata(long transactionObj, int key, Parcel data); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -413,6 +414,24 @@ public final class SurfaceControl implements Parcelable { } /** + * owner UID. + * @hide + */ + public static final int METADATA_OWNER_UID = 1; + + /** + * Window type as per {@link WindowManager.LayoutParams}. + * @hide + */ + public static final int METADATA_WINDOW_TYPE = 2; + + /** + * Task id to allow association between surfaces and task. + * @hide + */ + public static final int METADATA_TASK_ID = 3; + + /** * Builder class for {@link SurfaceControl} objects. */ public static class Builder { @@ -423,8 +442,7 @@ public final class SurfaceControl implements Parcelable { private int mFormat = PixelFormat.OPAQUE; private String mName; private SurfaceControl mParent; - private int mWindowType = -1; - private int mOwnerUid = -1; + private SparseIntArray mMetadata; /** * Begin building a SurfaceControl with a given {@link SurfaceSession}. @@ -455,8 +473,8 @@ public final class SurfaceControl implements Parcelable { throw new IllegalArgumentException( "Only buffer layers can set a valid buffer size."); } - return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat, - mFlags, mParent, mWindowType, mOwnerUid); + return new SurfaceControl( + mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata); } /** @@ -581,23 +599,17 @@ public final class SurfaceControl implements Parcelable { } /** - * Set surface metadata. + * Sets a metadata int. * - * Currently these are window-types as per {@link WindowManager.LayoutParams} and - * owner UIDs. Child surfaces inherit their parents - * metadata so only the WindowManager needs to set this on root Surfaces. - * - * @param windowType A window-type - * @param ownerUid UID of the window owner. + * @param key metadata key + * @param data associated data * @hide */ - public Builder setMetadata(int windowType, int ownerUid) { - if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) { - throw new UnsupportedOperationException( - "It only makes sense to set Surface metadata from the WindowManager"); + public Builder setMetadata(int key, int data) { + if (mMetadata == null) { + mMetadata = new SparseIntArray(); } - mWindowType = windowType; - mOwnerUid = ownerUid; + mMetadata.put(key, data); return this; } @@ -682,13 +694,12 @@ public final class SurfaceControl implements Parcelable { * @param h The surface initial height. * @param flags The surface creation flags. Should always include {@link #HIDDEN} * in the creation flags. - * @param windowType The type of the window as specified in WindowManager.java. - * @param ownerUid A unique per-app ID. + * @param metadata Initial metadata. * * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, - SurfaceControl parent, int windowType, int ownerUid) + SurfaceControl parent, SparseIntArray metadata) throws OutOfResourcesException, IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name must not be null"); @@ -706,8 +717,21 @@ public final class SurfaceControl implements Parcelable { mName = name; mWidth = w; mHeight = h; - mNativeObject = nativeCreate(session, name, w, h, format, flags, - parent != null ? parent.mNativeObject : 0, windowType, ownerUid); + Parcel metaParcel = Parcel.obtain(); + try { + if (metadata != null && metadata.size() > 0) { + metaParcel.writeInt(metadata.size()); + for (int i = 0; i < metadata.size(); ++i) { + metaParcel.writeInt(metadata.keyAt(i)); + metaParcel.writeByteArray( + ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array()); + } + } + mNativeObject = nativeCreate(session, name, w, h, format, flags, + parent != null ? parent.mNativeObject : 0, metaParcel); + } finally { + metaParcel.recycle(); + } if (mNativeObject == 0) { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); @@ -2326,6 +2350,30 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets an arbitrary piece of metadata on the surface. This is a helper for int data. + * @hide + */ + public Transaction setMetadata(int key, int data) { + Parcel parcel = Parcel.obtain(); + parcel.writeInt(data); + try { + setMetadata(key, parcel); + } finally { + parcel.recycle(); + } + return this; + } + + /** + * Sets an arbitrary piece of metadata on the surface. + * @hide + */ + public Transaction setMetadata(int key, Parcel data) { + nativeSetMetadata(mNativeObject, key, data); + return this; + } + + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f1dfc1c6072f..cd3decf4e981 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9067,35 +9067,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid=" + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + ", visible=" + (getVisibility() == VISIBLE) - + ": alreadyNotifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)); + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - final ViewStructure structure = session.newViewStructure(this); - onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; + + // The code below doesn't take much for a unique view, but it's called for all views + // the first time the view hiearchy is laid off, which could acccumulative delay the + // initial layout. Hence, we're postponing it to a later stage - it might still cost a + // lost frame (or more), but that jank cost would only happen after the 1st layout. + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + final ViewStructure structure = session.newViewStructure(this); + onProvideContentCaptureStructure(structure, /* flags= */ 0); + session.notifyViewAppeared(structure); + }, /* token= */ null); } else { if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0 || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) { if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { - Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this - + ": notifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid=" + + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + + ", visible=" + (getVisibility() == VISIBLE) + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - session.notifyViewDisappeared(getAutofillId()); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, + () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null); } } diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index fdc34b3f68d0..4d917a1b1968 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -115,7 +115,7 @@ public final class ActionsSuggestionsHelper { private int mNextUserId = FIRST_NON_LOCAL_USER; private int encode(Person person) { - if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) { + if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) { return USER_LOCAL; } Integer result = mMapping.get(person); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index f7c1a2640dc5..502181f633b6 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -109,9 +109,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_LOCAL = + public static final Person PERSON_USER_SELF = new Person.Builder() - .setKey("text-classifier-conversation-actions-local-user") + .setKey("text-classifier-conversation-actions-user-self") .build(); /** @@ -123,9 +123,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_REMOTE = + public static final Person PERSON_USER_OTHERS = new Person.Builder() - .setKey("text-classifier-conversation-actions-remote-user") + .setKey("text-classifier-conversation-actions-user-others") .build(); @Nullable @@ -235,10 +235,10 @@ public final class ConversationActions implements Parcelable { /** * Constructs a builder. * - * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * @param author the person that composed the message, use {@link #PERSON_USER_SELF} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use - * {@link #PERSON_USER_REMOTE} to represent a remote user. + * {@link #PERSON_USER_OTHERS} to represent a remote user. */ public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 42acb09d50d6..803462d59fad 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -18,6 +18,10 @@ package com.android.internal.app; import android.app.Activity; import android.app.ActivityManager; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -98,6 +102,20 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; + + /** + * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true, + * {@link AppPredictionManager} will be queried for direct share targets. + */ + // TODO(b/123089490): Replace with system flag + private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false; + // TODO(b/123088566) Share these in a better way. + private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; + private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; + public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; + private AppPredictor mAppPredictor; + private AppPredictor.Callback mAppPredictorCallback; + /** * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of * binding to every ChooserTargetService implementation. @@ -309,6 +327,35 @@ public class ChooserActivity extends ResolverActivity { mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost); + + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + final IntentFilter filter = getTargetIntentFilter(); + Bundle extras = new Bundle(); + extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter); + AppPredictionManager appPredictionManager = + getSystemService(AppPredictionManager.class); + mAppPredictor = appPredictionManager.createAppPredictionSession( + new AppPredictionContext.Builder(this) + .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT) + .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) + .setExtras(extras) + .build()); + mAppPredictorCallback = resultList -> { + final List<DisplayResolveInfo> driList = + getDisplayResolveInfos(mChooserListAdapter); + final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos = + new ArrayList<>(); + for (AppTarget appTarget : resultList) { + shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo( + appTarget.getShortcutInfo(), + new ComponentName( + appTarget.getPackageName(), appTarget.getClassName()))); + } + sendShareShortcutInfoList(shareShortcutInfos, driList); + }; + mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback); + } + if (DEBUG) { Log.d(TAG, "System Time Cost is " + systemCost); } @@ -339,6 +386,10 @@ public class ChooserActivity extends ResolverActivity { } unbindRemainingServices(); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback); + mAppPredictor.destroy(); + } } @Override @@ -606,15 +657,10 @@ public class ChooserActivity extends ResolverActivity { } } - private void queryDirectShareTargets(ChooserListAdapter adapter) { - final IntentFilter filter = getTargetIntentFilter(); - if (filter == null) { - return; - } - + private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) { // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo // and use the old code path. This Ugliness should go away when Sharesheet is refactored. - final List<DisplayResolveInfo> driList = new ArrayList<>(); + List<DisplayResolveInfo> driList = new ArrayList<>(); int targetsToQuery = 0; for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) { final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); @@ -634,40 +680,57 @@ public class ChooserActivity extends ResolverActivity { break; } } + return driList; + } + + private void queryDirectShareTargets(ChooserListAdapter adapter) { + if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { + mAppPredictor.requestPredictionUpdate(); + return; + } + final IntentFilter filter = getTargetIntentFilter(); + if (filter == null) { + return; + } + final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter); AsyncTask.execute(() -> { ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter); + sendShareShortcutInfoList(resultList, driList); + }); + } - // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path - // for direct share targets. After ShareSheet is refactored we should use the - // ShareShortcutInfos directly. - boolean resultMessageSent = false; - for (int i = 0; i < driList.size(); i++) { - List<ChooserTarget> chooserTargets = new ArrayList<>(); - for (int j = 0; j < resultList.size(); j++) { - if (driList.get(i).getResolvedComponentName().equals( + private void sendShareShortcutInfoList( + List<ShortcutManager.ShareShortcutInfo> resultList, + List<DisplayResolveInfo> driList) { + // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path + // for direct share targets. After ShareSheet is refactored we should use the + // ShareShortcutInfos directly. + boolean resultMessageSent = false; + for (int i = 0; i < driList.size(); i++) { + List<ChooserTarget> chooserTargets = new ArrayList<>(); + for (int j = 0; j < resultList.size(); j++) { + if (driList.get(i).getResolvedComponentName().equals( resultList.get(j).getTargetComponent())) { - chooserTargets.add(convertToChooserTarget(resultList.get(j))); - } + chooserTargets.add(convertToChooserTarget(resultList.get(j))); } - if (chooserTargets.isEmpty()) { - continue; - } - - final Message msg = Message.obtain(); - msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; - msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); - mChooserHandler.sendMessage(msg); - resultMessageSent = true; } - - if (resultMessageSent) { - final Message msg = Message.obtain(); - msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; - mChooserHandler.sendMessage(msg); + if (chooserTargets.isEmpty()) { + continue; } - }); + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT; + msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null); + mChooserHandler.sendMessage(msg); + resultMessageSent = true; + } + + if (resultMessageSent) { + final Message msg = Message.obtain(); + msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; + mChooserHandler.sendMessage(msg); + } } private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) { @@ -724,6 +787,7 @@ public class ChooserActivity extends ResolverActivity { // Do nothing. We'll send the voice stuff ourselves. } + // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor void updateModelAndChooserCounts(TargetInfo info) { if (info != null) { final ResolveInfo ri = info.getResolveInfo(); diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 514ff76372a9..d7514d1fe26c 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -154,4 +154,7 @@ interface IBatteryStats { oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info); oneway void noteModemControllerActivity(in ModemActivityInfo info); oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info); + + /** {@hide} */ + boolean setChargingStateUpdateDelayMillis(int delay); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 534361e13c7d..c6afee24cfb5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13395,11 +13395,22 @@ public class BatteryStatsImpl extends BatteryStats { mResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS), false /* notifyForDescendants */, this); + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY), + false /* notifyForDescendants */, this); updateConstants(); } @Override public void onChange(boolean selfChange, Uri uri) { + if (uri.equals( + Settings.Global.getUriFor( + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) { + synchronized (BatteryStatsImpl.this) { + updateBatteryChargedDelayMsLocked(); + } + return; + } updateConstants(); } @@ -13443,12 +13454,21 @@ public class BatteryStatsImpl extends BatteryStats { DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; - BATTERY_CHARGED_DELAY_MS = mParser.getInt( - KEY_BATTERY_CHARGED_DELAY_MS, - DEFAULT_BATTERY_CHARGED_DELAY_MS); + updateBatteryChargedDelayMsLocked(); } } + private void updateBatteryChargedDelayMsLocked() { + // a negative value indicates that we should ignore this override + final int delay = Settings.Global.getInt(mResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + -1); + + BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt( + KEY_BATTERY_CHARGED_DELAY_MS, + DEFAULT_BATTERY_CHARGED_DELAY_MS); + } + private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) { TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled; if (isEnabled && !wasEnabled) { diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index dd7633ac6269..acb34ba3dfec 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" diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp index 38f7a7e25389..3204317cab68 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.cpp +++ b/core/jni/android_media_MediaMetricsJNI.cpp @@ -1,90 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <android_runtime/AndroidRuntime.h> -#include <jni.h> -#include <nativehelper/JNIHelp.h> - -#include "android_media_MediaMetricsJNI.h" -#include <media/MediaAnalyticsItem.h> - - -namespace android { - -// place the attributes into a java PersistableBundle object -jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) { - - jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); - if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); - return NULL; - } - // sometimes the caller provides one for us to fill - if (mybundle == NULL) { - // create the bundle - jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); - mybundle = env->NewObject(clazzBundle, constructID); - if (mybundle == NULL) { - return NULL; - } - } - - // grab methods that we can invoke - jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); - jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); - jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); - jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); - - // env, class, method, {parms} - //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint); - - // iterate through my attributes - // -- get name, get type, get value - // -- insert appropriately into the bundle - for (size_t i = 0 ; i < item->mPropCount; i++ ) { - MediaAnalyticsItem::Prop *prop = &item->mProps[i]; - // build the key parameter from prop->mName - jstring keyName = env->NewStringUTF(prop->mName); - // invoke the appropriate method to insert - switch (prop->mType) { - case MediaAnalyticsItem::kTypeInt32: - env->CallVoidMethod(mybundle, setIntID, - keyName, (jint) prop->u.int32Value); - break; - case MediaAnalyticsItem::kTypeInt64: - env->CallVoidMethod(mybundle, setLongID, - keyName, (jlong) prop->u.int64Value); - break; - case MediaAnalyticsItem::kTypeDouble: - env->CallVoidMethod(mybundle, setDoubleID, - keyName, (jdouble) prop->u.doubleValue); - break; - case MediaAnalyticsItem::kTypeCString: - env->CallVoidMethod(mybundle, setStringID, keyName, - env->NewStringUTF(prop->u.CStringValue)); - break; - default: - ALOGE("to_String bad item type: %d for %s", - prop->mType, prop->mName); - break; - } - } - - return mybundle; -} - -}; // namespace android - +../../media/jni/android_media_MediaMetricsJNI.cpp
\ No newline at end of file diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h index b3cb4d293399..c7a685beb7e5 100644..120000 --- a/core/jni/android_media_MediaMetricsJNI.h +++ b/core/jni/android_media_MediaMetricsJNI.h @@ -1,33 +1 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ -#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ - -#include <jni.h> -#include <nativehelper/JNIHelp.h> -#include <media/MediaAnalyticsItem.h> - -namespace android { - -class MediaMetricsJNI { -public: - static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); -}; - -}; // namespace android - -#endif // _ANDROID_MEDIA_MEDIAMETRICSJNI_H_ +../../media/jni/android_media_MediaMetricsJNI.h
\ No newline at end of file diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 69877c7d3930..f1b259e10cf5 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -124,7 +124,7 @@ static jlong nativeGetNativeTransactionFinalizer(JNIEnv* env, jclass clazz) { static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, - jint windowType, jint ownerUid) { + jobject metadataParcel) { ScopedUtfChars name(env, nameStr); sp<SurfaceComposerClient> client; if (sessionObj != NULL) { @@ -134,8 +134,18 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, } SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface; + LayerMetadata metadata; + Parcel* parcel = parcelForJavaObject(env, metadataParcel); + if (parcel && !parcel->objectsCount()) { + status_t err = metadata.readFromParcel(parcel); + if (err != NO_ERROR) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Metadata parcel has wrong format"); + } + } + status_t err = client->createSurfaceChecked( - String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid); + String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata)); if (err == NAME_NOT_FOUND) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return 0; @@ -377,6 +387,28 @@ static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactio transaction->transferTouchFocus(fromToken, toToken); } +static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint id, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (!parcel) { + jniThrowNullPointerException(env, "attribute data"); + return; + } + if (parcel->objectsCount()) { + jniThrowException(env, "java/lang/RuntimeException", + "Tried to marshall a Parcel that contained Binder objects."); + return; + } + + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + std::vector<uint8_t> byteData(parcel->dataSize()); + memcpy(byteData.data(), parcel->data(), parcel->dataSize()); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setMetadata(ctrl, id, std::move(byteData)); +} + static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jfloatArray fColor) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -981,7 +1013,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz, // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { - {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J", + {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", (void*)nativeCreate }, {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel }, @@ -1099,6 +1131,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetInputWindowInfo }, {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V", (void*)nativeTransferTouchFocus }, + {"nativeSetMetadata", "(JILandroid/os/Parcel;)V", + (void*)nativeSetMetadata }, {"nativeGetDisplayedContentSamplingAttributes", "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;", (void*)nativeGetDisplayedContentSamplingAttributes }, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 8681d4b3f42e..6ee960668a3e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -821,7 +821,7 @@ static bool NeedsNoRandomizeWorkaround() { // Utility to close down the Zygote socket file descriptors while // the child is still running as root with Zygote's privileges. Each -// descriptor (if any) is closed via dup2(), replacing it with a valid +// descriptor (if any) is closed via dup3(), replacing it with a valid // (open) descriptor to /dev/null. static void DetachDescriptors(JNIEnv* env, @@ -829,15 +829,15 @@ static void DetachDescriptors(JNIEnv* env, fail_fn_t fail_fn) { if (fds_to_close.size() > 0) { - android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR)); + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC)); if (devnull_fd == -1) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } for (int fd : fds_to_close) { ALOGV("Switching descriptor %d to /dev/null", fd); - if (dup2(devnull_fd, fd) == -1) { - fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno))); + if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) { + fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno))); } } } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 4e486630adae..4b37f13cbb33 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -423,13 +423,13 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { } void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { - const int dev_null_fd = open("/dev/null", O_RDWR); + const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); if (dev_null_fd < 0) { fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - if (dup2(dev_null_fd, fd) == -1) { - fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", + if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) { + fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s", fd, strerror(errno))); } diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index f06165cc7e00..7e7942e6ddf1 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -522,6 +522,8 @@ message GlobalSettingsProto { optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Packages that are whitelisted for ignoring location settings (during emergencies) + optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Location location = 69; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 49f2c84335c5..c05795de4751 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2211,6 +2211,10 @@ has expired, then assume the device is receiving insufficient current to charge effectively and terminate the dream. Use -1 to disable this safety feature. --> <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> + <!-- Limit of how long the device can remain unlocked due to attention checking. --> + <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes --> + <!-- How long we should wait until we give up on receiving an attention API callback. --> + <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds --> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d5561302fdc6..f79e22d1f94e 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/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index a15dbc80d7db..bd7f8527fc6f 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -131,6 +131,7 @@ public class SettingsBackupTest { Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, Settings.Global.AUTOMATIC_POWER_SAVER_MODE, Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index 780e15ab885e..5022e305ecc2 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,8 +16,8 @@ package android.view.textclassifier; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; import static com.google.common.truth.Truth.assertThat; @@ -58,7 +58,7 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); + new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( @@ -81,7 +81,7 @@ public class ActionsSuggestionsHelperTest { .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_LOCAL) + new ConversationActions.Message.Builder(PERSON_USER_SELF) .setText("third") .build(); ConversationActions.Message fourthMessage = @@ -104,16 +104,16 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("first") .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("third") .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 4d78e4036e74..5e58f82038f1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -378,7 +378,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = @@ -407,7 +407,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3c35d9b33fc8..20303eba6667 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -562,7 +562,7 @@ std::string AssetManager2::GetLastResourceResolution() const { if (package != nullptr) { ToResourceName(last_resolution.type_string_ref, last_resolution.entry_string_ref, - package, + package->GetPackageName(), &resource_name); resource_name_string = ToFormattedResourceString(&resource_name); } @@ -607,15 +607,25 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons return false; } - const LoadedPackage* package = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); - if (package == nullptr) { + const uint8_t package_idx = package_ids_[get_package_id(resid)]; + if (package_idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + get_package_id(resid), resid); + return false; + } + + const PackageGroup& package_group = package_groups_[package_idx]; + auto cookie_iter = std::find(package_group.cookies_.begin(), + package_group.cookies_.end(), cookie); + if (cookie_iter == package_group.cookies_.end()) { return false; } + long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter); + const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_; return ToResourceName(entry.type_string_ref, entry.entry_string_ref, - package, + package->GetPackageName(), out_name); } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 645984d85c34..c63dff8f9104 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -48,12 +48,12 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin !(has_type_separator && out_type->empty()); } -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name) { - out_name->package = package->GetPackageName().data(); - out_name->package_len = package->GetPackageName().size(); + out_name->package = package_name.data(); + out_name->package_len = package_name.size(); out_name->type = type_string_ref.string8(&out_name->type_len); out_name->type16 = nullptr; diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index eb6eb8e66175..e649940cdde1 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,12 +28,11 @@ namespace android { bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); -// Convert a type_string_ref, entry_string_ref, and package -// to AssetManager2::ResourceName. Useful for getting -// resource name without re-running AssetManager2::FindEntry searches. -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. +// Useful for getting resource name without re-running AssetManager2::FindEntry searches. +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name); // Formats a ResourceName to "package:type/entry_name". diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 105dcd209bf7..447fdf5d306a 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -210,6 +210,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({lib_one_assets_.get()}); + + AssetManager2::ResourceName name; + ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name)); + std::string formatted_name = ToFormattedResourceString(&name); + ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo"); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index d2903f08af15..2f2d575bca29 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -99,7 +99,7 @@ void Paint::setAntiAlias(bool aa) { ////////////////// Java flags compatibility ////////////////// -/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really match up with skia anymore, so we have to do some shuffling in get/set flags() 3 flags apply to SkPaint (antialias, dither, filter -> enum) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b7f042b48da1..f996d38c86a9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -30,6 +30,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -3989,33 +3990,11 @@ public class AudioManager { } /** - * Indicate A2DP source or sink connection state change. - * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) - * @param profile profile for the A2DP device - * (either {@link android.bluetooth.BluetoothProfile.A2DP} or - * {@link android.bluetooth.BluetoothProfile.A2DP_SINK}) - * @return a delay in ms that the caller should wait before broadcasting - * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent. - * {@hide} - */ - public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, - int profile) { - final IAudioService service = getService(); - int delay = 0; - try { - delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return delay; - } - - /** * Indicate A2DP source or sink connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) + * @param state new connection state, {@link BluetoothProfile#STATE_CONNECTED} + * or {@link BluetoothProfile#STATE_DISCONNECTED} * @param profile profile for the A2DP device * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting. * (either {@link android.bluetooth.BluetoothProfile.A2DP} or @@ -4027,8 +4006,8 @@ public class AudioManager { * {@hide} */ public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - BluetoothDevice device, int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { + BluetoothDevice device, int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { final IAudioService service = getService(); int delay = 0; try { diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index af016d5d4be9..ffa3b247480a 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -39,6 +39,8 @@ import java.util.Map; */ public class AudioSystem { + private static final boolean DEBUG_VOLUME = true; + private static final String TAG = "AudioSystem"; /* These values must be kept in sync with system/audio.h */ /* @@ -879,6 +881,15 @@ public class AudioSystem } } + /** Wrapper for native methods called from AudioService */ + public static int setStreamVolumeIndexAS(int stream, int index, int device) { + if (DEBUG_VOLUME) { + Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream] + + " dev=" + Integer.toHexString(device) + " idx=" + index); + } + return setStreamVolumeIndex(stream, index, device); + } + // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t public static final int SYNC_EVENT_NONE = 0; public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; @@ -906,7 +917,7 @@ public class AudioSystem @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage - public static native int setStreamVolumeIndex(int stream, int index, int device); + private static native int setStreamVolumeIndex(int stream, int index, int device); public static native int getStreamVolumeIndex(int stream, int device); public static native int setMasterVolume(float value); public static native float getMasterVolume(); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 14bdab98d46b..f5aeca717424 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -151,8 +151,6 @@ interface IAudioService { void setWiredDeviceConnectionState(int type, int state, String address, String name, String caller); - int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile); - void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device, diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 48dbf555e546..852d2962ee66 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -101,7 +101,7 @@ cc_library_shared { "libhidlbase", "libhidlmemory", - "libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API. + "libmediametrics", "libbinder", // Used by JWakeLock and MediaMetrics. "libutils", // Have to use shared lib to make libandroid_runtime behave correctly. diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp index 3ded8c260512..de60b085b87d 100644 --- a/media/jni/android_media_MediaMetricsJNI.cpp +++ b/media/jni/android_media_MediaMetricsJNI.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "MediaMetricsJNI" + #include <jni.h> #include <nativehelper/JNIHelp.h> @@ -21,7 +23,10 @@ #include <media/MediaAnalyticsItem.h> -// Copeid from core/jni/ (libandroid_runtime.so) +// This source file is compiled and linked into both: +// core/jni/ (libandroid_runtime.so) +// media/jni (libmedia2_jni.so) + namespace android { // place the attributes into a java PersistableBundle object @@ -29,7 +34,7 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); if (clazzBundle==NULL) { - ALOGD("can't find android/os/PersistableBundle"); + ALOGE("can't find android/os/PersistableBundle"); return NULL; } // sometimes the caller provides one for us to fill @@ -86,5 +91,138 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i return mybundle; } +// convert the specified batch metrics attributes to a persistent bundle. +// The encoding of the byte array is specified in +// frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +// +// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp +enum { kInt32 = 0, kInt64, kDouble, kRate, kCString}; + +jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) { + ALOGV("writeAttributes()"); + + if (buffer == NULL || length <= 0) { + ALOGW("bad parameters to writeAttributesToBundle()"); + return NULL; + } + + jclass clazzBundle = env->FindClass("android/os/PersistableBundle"); + if (clazzBundle==NULL) { + ALOGE("can't find android/os/PersistableBundle"); + return NULL; + } + // sometimes the caller provides one for us to fill + if (mybundle == NULL) { + // create the bundle + jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V"); + mybundle = env->NewObject(clazzBundle, constructID); + if (mybundle == NULL) { + ALOGD("unable to create mybundle"); + return NULL; + } + } + + int left = length; + char *buf = buffer; + + // grab methods that we can invoke + jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V"); + jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V"); + jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V"); + jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"); + + +#define _EXTRACT(size, val) \ + { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);} +#define _SKIP(size) \ + { if ((size) > left) goto badness; buf += (size); left -= (size);} + + int32_t bufsize; + _EXTRACT(sizeof(int32_t), bufsize); + if (bufsize != length) { + goto badness; + } + int32_t proto; + _EXTRACT(sizeof(int32_t), proto); + if (proto != 0) { + ALOGE("unsupported wire protocol %d", proto); + goto badness; + } + + int32_t count; + _EXTRACT(sizeof(int32_t), count); + + // iterate through my attributes + // -- get name, get type, get value, insert into bundle appropriately. + for (int i = 0 ; i < count; i++ ) { + // prop name len (int16) + int16_t keylen; + _EXTRACT(sizeof(int16_t), keylen); + if (keylen <= 0) goto badness; + // prop name itself + char *key = buf; + jstring keyName = env->NewStringUTF(buf); + _SKIP(keylen); + + // prop type (int8_t) + int8_t attrType; + _EXTRACT(sizeof(int8_t), attrType); + + int16_t attrSize; + _EXTRACT(sizeof(int16_t), attrSize); + + switch (attrType) { + case kInt32: + { + int32_t i32; + _EXTRACT(sizeof(int32_t), i32); + env->CallVoidMethod(mybundle, setIntID, + keyName, (jint) i32); + break; + } + case kInt64: + { + int64_t i64; + _EXTRACT(sizeof(int64_t), i64); + env->CallVoidMethod(mybundle, setLongID, + keyName, (jlong) i64); + break; + } + case kDouble: + { + double d64; + _EXTRACT(sizeof(double), d64); + env->CallVoidMethod(mybundle, setDoubleID, + keyName, (jdouble) d64); + break; + } + case kCString: + { + jstring value = env->NewStringUTF(buf); + env->CallVoidMethod(mybundle, setStringID, + keyName, value); + _SKIP(attrSize); + break; + } + default: + ALOGW("ignoring Attribute '%s' unknown type: %d", + key, attrType); + _SKIP(attrSize); + break; + } + } + + // should have consumed it all + if (left != 0) { + ALOGW("did not consume entire buffer; left(%d) != 0", left); + goto badness; + } + + return mybundle; + + badness: + return NULL; +} + }; // namespace android diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h index fd621ea7261d..a10780f5c5c3 100644 --- a/media/jni/android_media_MediaMetricsJNI.h +++ b/media/jni/android_media_MediaMetricsJNI.h @@ -27,6 +27,7 @@ namespace android { class MediaMetricsJNI { public: static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle); + static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length); }; }; // namespace android diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 9b4e730cfb5e..306916121740 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -786,22 +786,17 @@ android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz) return 0; } - Parcel p; - int key = FOURCC('m','t','r','X'); - status_t status = mp->getParameter(key, &p); + char *buffer = NULL; + size_t length = 0; + status_t status = mp->getMetrics(&buffer, &length); if (status != OK) { ALOGD("getMetrics() failed: %d", status); return (jobject) NULL; } - p.setDataPosition(0); - MediaAnalyticsItem *item = new MediaAnalyticsItem; - item->readFromParcel(p); - jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); + jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length); - // housekeeping - delete item; - item = NULL; + free(buffer); return mybundle; } diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index bc6e2fc7fc48..5acf4fbaa5cb 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -291,7 +291,7 @@ public class SmartActionsHelper { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages == null || messages.length == 0) { return Arrays.asList(new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) .build()); } @@ -310,7 +310,7 @@ public class SmartActionsHelper { break; } Person author = localUser != null && localUser.equals(senderPerson) - ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + ? ConversationActions.Message.PERSON_USER_SELF : senderPerson; extractMessages.push(new ConversationActions.Message.Builder(author) .setText(message.getText()) .setReferenceTime( diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 707349b0fd15..7f8127aa43a8 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -154,7 +154,7 @@ public class SmartActionHelperTest { ConversationActions.Message secondMessage = messages.get(0); MessageSubject.assertThat(secondMessage).hasText("secondMessage"); MessageSubject.assertThat(secondMessage) - .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + .hasPerson(ConversationActions.Message.PERSON_USER_SELF); MessageSubject.assertThat(secondMessage) .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index aff6f0452533..4f6a4ad94479 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -822,6 +822,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS, GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS); + dumpSetting(s, p, + Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, + GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST); p.end(locationToken); final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 5fdf76f6f0bc..b584f6781796 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,6 +33,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -590,9 +591,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private int getStatusBarHeight() { if (getRootWindowInsets() != null) { + WindowInsets insets = getRootWindowInsets(); return Math.max( - getRootWindowInsets().getSystemWindowInsetTop(), - getRootWindowInsets().getDisplayCutout().getSafeInsetTop()); + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index f3ca9386c312..4f870f6ceffc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -98,7 +98,9 @@ public class ExpandedAnimationController if (insets != null) { return mBubblePaddingPx + Math.max( insets.getSystemWindowInsetTop(), - insets.getDisplayCutout().getSafeInsetTop()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); } return mBubblePaddingPx; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index a113a630dfd8..0f5137618258 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -211,7 +211,9 @@ public class StackAnimationController extends - mBubblePadding + Math.max( insets.getSystemWindowInsetLeft(), - insets.getDisplayCutout().getSafeInsetLeft()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetLeft() + : 0); mAllowableStackPositionRegion.right = mLayout.getWidth() - mIndividualBubbleSize @@ -219,20 +221,26 @@ public class StackAnimationController extends - mBubblePadding - Math.max( insets.getSystemWindowInsetRight(), - insets.getDisplayCutout().getSafeInsetRight()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetRight() + : 0); mAllowableStackPositionRegion.top = mBubblePadding + Math.max( insets.getSystemWindowInsetTop(), - insets.getDisplayCutout().getSafeInsetTop()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); mAllowableStackPositionRegion.bottom = mLayout.getHeight() - mIndividualBubbleSize - mBubblePadding - Math.max( insets.getSystemWindowInsetBottom(), - insets.getDisplayCutout().getSafeInsetBottom()); + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() + : 0); } return mAllowableStackPositionRegion; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 0f686df87ca5..db819d57417b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -30,6 +30,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Spy; @@ -89,6 +90,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase } @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") public void testFlingSideways() throws InterruptedException { // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top @@ -119,6 +121,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase } @Test + @Ignore("Sporadically failing due to DynamicAnimation not settling.") public void testFlingUpFromBelowBottomCenter() throws InterruptedException { // Move to the center of the screen, just past the bottom. mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a96676e5fc5f..5d6c2f074f9d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -677,6 +677,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -1649,6 +1650,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName); intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index a376e7a15410..08900328a200 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.app.ActivityManager; import android.app.job.JobProtoEnums; import android.bluetooth.BluetoothActivityEnergyInfo; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -46,6 +47,7 @@ import android.os.connectivity.WifiBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; import android.os.health.UidHealthStats; +import android.provider.Settings; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; @@ -1651,4 +1653,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub return new HealthStatsParceler(uidWriter); } + /** + * Delay for sending ACTION_CHARGING after device is plugged in. + * + * @hide + */ + public boolean setChargingStateUpdateDelayMillis(int delayMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null); + final long ident = Binder.clearCallingIdentity(); + + try { + final ContentResolver contentResolver = mContext.getContentResolver(); + return Settings.Global.putLong(contentResolver, + Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + delayMillis); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 353749f211c1..cdf6e0ede865 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -776,6 +776,7 @@ public final class BroadcastQueue { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 27edbbf4f2d5..b71a7517ab12 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -174,10 +174,11 @@ public class AttentionManagerService extends SystemService { @Override public void onSuccess(int requestCode, int result, long timestamp) { callback.onSuccess(requestCode, result, timestamp); - userState.mAttentionCheckCache = new AttentionCheckCache( - SystemClock.uptimeMillis(), result, - timestamp); - + synchronized (mLock) { + userState.mAttentionCheckCache = new AttentionCheckCache( + SystemClock.uptimeMillis(), result, + timestamp); + } StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, result); } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java new file mode 100644 index 000000000000..d652f93ccf04 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -0,0 +1,807 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** @hide */ +/*package*/ final class AudioDeviceBroker { + + private static final String TAG = "AudioDeviceBroker"; + + private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s + + /*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000; + // Timeout for connection to bluetooth headset service + /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; + + private final @NonNull AudioService mAudioService; + private final @NonNull Context mContext; + + /** Forced device usage for communications sent to AudioSystem */ + private int mForcedUseForComm; + /** + * Externally reported force device usage state returned by getters: always consistent + * with requests by setters */ + private int mForcedUseForCommExt; + + // Manages all connected devices, only ever accessed on the message loop + //### or make it synchronized + private final AudioDeviceInventory mDeviceInventory; + // Manages notifications to BT service + private final BtHelper mBtHelper; + + + //------------------------------------------------------------------- + private static final Object sLastDeviceConnectionMsgTimeLock = new Object(); + private static long sLastDeviceConnectMsgTime = 0; + + private final Object mBluetoothA2dpEnabledLock = new Object(); + // Request to override default use of A2DP for media. + @GuardedBy("mBluetoothA2dpEnabledLock") + private boolean mBluetoothA2dpEnabled; + + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mA2dpAvrcpLock = new Object(); + // lock always taken synchronized on mConnectedDevices + /*package*/ final Object mHearingAidLock = new Object(); + + // lock always taken when accessing AudioService.mSetModeDeathHandlers + /*package*/ final Object mSetModeLock = new Object(); + + //------------------------------------------------------------------- + /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { + mContext = context; + mAudioService = service; + setupMessaging(context); + mBtHelper = new BtHelper(this); + mDeviceInventory = new AudioDeviceInventory(this); + + mForcedUseForComm = AudioSystem.FORCE_NONE; + mForcedUseForCommExt = mForcedUseForComm; + + } + + /*package*/ Context getContext() { + return mContext; + } + + //--------------------------------------------------------------------- + // Communication from AudioService + // All methods are asynchronous and never block + // All permission checks are done in AudioService, all incoming calls are considered "safe" + // All post* methods are asynchronous + + /*package*/ void onSystemReady() { + mBtHelper.onSystemReady(); + } + + /*package*/ void onAudioServerDied() { + // Restore forced usage for communications and record + onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied"); + onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied"); + // restore devices + sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE); + } + + /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) { + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + useCase, config, eventSource); + } + + /*package*/ void toggleHdmiIfConnected_Async() { + sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE); + } + + /*package*/ void disconnectAllBluetoothProfiles() { + mBtHelper.disconnectAllBluetoothProfiles(); + } + + /** + * Handle BluetoothHeadset intents where the action is one of + * {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or + * {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}. + * @param intent + */ + /*package*/ void receiveBtEvent(@NonNull Intent intent) { + mBtHelper.receiveBtEvent(intent); + } + + /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { + synchronized (mBluetoothA2dpEnabledLock) { + if (mBluetoothA2dpEnabled == on) { + return; + } + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + source); + } + } + + /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) { + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + + mForcedUseForCommExt = mForcedUseForComm; + setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + } + + /*package*/ boolean isSpeakerphoneOn() { + return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + } + + /*package*/ void setWiredDeviceConnectionState(int type, + @AudioService.ConnectionState int state, String address, String name, + String caller) { + //TODO move logging here just like in setBluetooth* methods + mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + } + + /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state + // only querying address as this is the only readily available field + // on the device + + " addr=" + device.getAddress() + + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent + + " vol=" + a2dpVolume)).printLog(TAG)); + if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(device))) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP connection state ignored")); + return 0; + } + return mDeviceInventory.setBluetoothA2dpDeviceConnectionState( + device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "setHearingAidDeviceConnectionState state=" + state + + " addr=" + device.getAddress() + + " supprNoisy=" + suppressNoisyIntent + + " src=" + eventSource)).printLog(TAG)); + return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice); + } + + // never called by system components + /*package*/ void setBluetoothScoOnByApp(boolean on) { + mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + } + + /*package*/ boolean isBluetoothScoOnForApp() { + return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO; + } + + /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { + //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); + if (on) { + // do not accept SCO ON if SCO audio is not connected + if (!mBtHelper.isBluetoothScoOn()) { + mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; + return; + } + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + mForcedUseForCommExt = mForcedUseForComm; + AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off")); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); + sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); + // Un-mute ringtone stream volume + mAudioService.setUpdateRingerModeServiceInt(); + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + return mDeviceInventory.startWatchingRoutes(observer); + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mDeviceInventory.getCurAudioRoutes(); + } + + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.isAvrcpAbsoluteVolumeSupported(); + } + } + + /*package*/ boolean isBluetoothA2dpOn() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) { + sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index); + } + + /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) { + sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType); + } + + /*package*/ void postDisconnectBluetoothSco(int exceptPid) { + sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid); + } + + /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { + sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); + } + + /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource); + } + + /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) { + mBtHelper.stopBluetoothScoForClient(cb, eventSource); + } + + //--------------------------------------------------------------------- + // Communication with (to) AudioService + //TODO check whether the AudioService methods are candidates to move here + /*package*/ void postAccessoryPlugMediaUnmute(int device) { + mAudioService.postAccessoryPlugMediaUnmute(device); + } + + /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) { + return mAudioService.getStreamState(streamType); + } + + /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() { + return mAudioService.mSetModeDeathHandlers; + } + + /*package*/ int getDeviceForStream(int streamType) { + return mAudioService.getDeviceForStream(streamType); + } + + /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) { + mAudioService.setDeviceVolume(streamState, device); + } + + /*packages*/ void observeDevicesForAllStreams() { + mAudioService.observeDevicesForAllStreams(); + } + + /*package*/ boolean isInCommunication() { + return mAudioService.isInCommunication(); + } + + /*package*/ boolean hasMediaDynamicPolicy() { + return mAudioService.hasMediaDynamicPolicy(); + } + + /*package*/ ContentResolver getContentResolver() { + return mAudioService.getContentResolver(); + } + + /*package*/ void checkMusicActive(int deviceType, String caller) { + mAudioService.checkMusicActive(deviceType, caller); + } + + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + mAudioService.checkVolumeCecOnHdmiConnection(state, caller); + } + + //--------------------------------------------------------------------- + // Message handling on behalf of helper classes + /*package*/ void broadcastScoConnectionState(int state) { + sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state); + } + + /*package*/ void broadcastBecomingNoisy() { + sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); + } + + //###TODO unify with handleSetA2dpSinkConnectionState + /*package*/ void postA2dpSinkConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + //###TODO unify with handleSetA2dpSourceConnectionState + /*package*/ void postA2dpSourceConnection(int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { + sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void postSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) { + sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay); + } + + /*package*/ void postSetHearingAidConnectionState( + @AudioService.BtProfileConnectionState int state, + @NonNull BluetoothDevice device, int delay) { + sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE, + state, + device, + delay); + } + + //--------------------------------------------------------------------- + // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory) + // only call from a "handle"* method or "on"* method + + // Handles request to override default use of A2DP for media. + //@GuardedBy("mConnectedDevices") + /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) { + // for logging only + final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).append(" src:").append(source).toString(); + + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); + onSetForceUse( + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + eventSource); + } + } + + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + } + + /*package*/ void handleDisconnectA2dp() { + mDeviceInventory.disconnectA2dp(); + } + /*package*/ void handleDisconnectA2dpSink() { + mDeviceInventory.disconnectA2dpSink(); + } + + /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version? + // might be ok here because called on BT thread? + sync happening in + // checkSendBecomingNoisyIntent + final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, + AudioSystem.DEVICE_NONE); + final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo + + " state= " + state + + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock()); + } + sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, + state, btDeviceInfo, delay); + } + + /*package*/ void handleDisconnectHearingAid() { + mDeviceInventory.disconnectHearingAid(); + } + + /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, + @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state, + btDeviceInfo); + } + + /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) { + sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay); + } + + /*package*/ void handleCancelFailureToConnectToBtHeadsetService() { + mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + } + + /*package*/ void postReportNewRoutes() { + sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP); + } + + /*package*/ void cancelA2dpDockTimeout() { + mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { + sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpDockTimeout() { + return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); + } + + //### + // must be called synchronized on mConnectedDevices + /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { + return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, + new BtHelper.BluetoothA2dpDeviceInfo(btDevice)); + } + + /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { + sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); + } + + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + synchronized (mA2dpAvrcpLock) { + mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); + } + } + + /*package*/ boolean getBluetoothA2dpEnabled() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + synchronized (mA2dpAvrcpLock) { + return mBtHelper.getA2dpCodec(device); + } + } + + //--------------------------------------------------------------------- + // Internal handling of messages + // These methods are ALL synchronous, in response to message handling in BrokerHandler + // Blocking in any of those will block the message queue + + private void onSetForceUse(int useCase, int config, String eventSource) { + if (useCase == AudioSystem.FOR_MEDIA) { + postReportNewRoutes(); + } + AudioService.sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } + + private void onSendBecomingNoisyIntent() { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + } + + //--------------------------------------------------------------------- + // Message handling + private BrokerHandler mBrokerHandler; + private BrokerThread mBrokerThread; + private PowerManager.WakeLock mBrokerEventWakeLock; + + private void setupMessaging(Context ctxt) { + final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE); + mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "handleAudioDeviceEvent"); + mBrokerThread = new BrokerThread(); + mBrokerThread.start(); + waitForBrokerHandlerCreation(); + } + + private void waitForBrokerHandlerCreation() { + synchronized (this) { + while (mBrokerHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + Log.e(TAG, "Interruption while waiting on BrokerHandler"); + } + } + } + } + + /** Class that handles the device broker's message queue */ + private class BrokerThread extends Thread { + BrokerThread() { + super("AudioDeviceBroker"); + } + + @Override + public void run() { + // Set this thread up so the handler will work on it + Looper.prepare(); + + synchronized (AudioDeviceBroker.this) { + mBrokerHandler = new BrokerHandler(); + + // Notify that the handler has been created + AudioDeviceBroker.this.notify(); + } + + Looper.loop(); + } + } + + /** Class that handles the message queue */ + private class BrokerHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_RESTORE_DEVICES: + mDeviceInventory.onRestoreDevices(); + synchronized (mBluetoothA2dpEnabledLock) { + mBtHelper.onAudioServerDiedRestoreA2dp(); + } + break; + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + mDeviceInventory.onSetWiredDeviceConnectionState( + (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj); + break; + case MSG_I_BROADCAST_BT_CONNECTION_STATE: + mBtHelper.onBroadcastScoConnectionState(msg.arg1); + break; + case MSG_IIL_SET_FORCE_USE: // intented fall-through + case MSG_IIL_SET_FORCE_BT_A2DP_USE: + onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj); + break; + case MSG_REPORT_NEW_ROUTES: + mDeviceInventory.onReportNewRoutes(); + break; + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSinkConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2); + break; + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + mDeviceInventory.onSetA2dpSourceConnectionState( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); + break; + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + mDeviceInventory.onSetHearingAidConnectionState( + (BluetoothDevice) msg.obj, msg.arg1); + break; + case MSG_BT_HEADSET_CNCT_FAILED: + mBtHelper.resetBluetoothSco(); + break; + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + // msg.obj == address of BTA2DP device + mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); + break; + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + final int a2dpCodec; + final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + synchronized (mA2dpAvrcpLock) { + a2dpCodec = mBtHelper.getA2dpCodec(btDevice); + } + mDeviceInventory.onBluetoothA2dpDeviceConfigChange( + new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec)); + break; + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; + case MSG_II_SET_HEARING_AID_VOLUME: + mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2); + break; + case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME: + mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1); + break; + case MSG_I_DISCONNECT_BT_SCO: + mBtHelper.disconnectBluetoothSco(msg.arg1); + break; + case MSG_TOGGLE_HDMI: + mDeviceInventory.onToggleHdmi(); + break; + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + mDeviceInventory.onBluetoothA2dpActiveDeviceChange( + (BtHelper.BluetoothA2dpDeviceInfo) msg.obj); + break; + default: + Log.wtf(TAG, "Invalid message " + msg.what); + } + if (isMessageHandledUnderWakelock(msg.what)) { + try { + mBrokerEventWakeLock.release(); + } catch (Exception e) { + Log.e(TAG, "Exception releasing wakelock", e); + } + } + } + } + + // List of all messages. If a message has be handled under wakelock, add it to + // the isMessageHandledUnderWakelock(int) method + // Naming of msg indicates arguments, using JNI argument grammar + // (e.g. II indicates two int args, IL indicates int and Obj arg) + private static final int MSG_RESTORE_DEVICES = 1; + private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2; + private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; + private static final int MSG_IIL_SET_FORCE_USE = 4; + private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; + private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6; + private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; + private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; + private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; + private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10; + private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; + private static final int MSG_REPORT_NEW_ROUTES = 13; + private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; + private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; + private static final int MSG_I_DISCONNECT_BT_SCO = 16; + private static final int MSG_TOGGLE_HDMI = 17; + private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; + + + private static boolean isMessageHandledUnderWakelock(int msgId) { + switch(msgId) { + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_TOGGLE_HDMI: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + return true; + default: + return false; + } + } + + // Message helper methods + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private void sendMsg(int msg, int existingMsgPolicy, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay); + } + + private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay); + } + + private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay); + } + + private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay); + } + + private void sendMsgNoDelay(int msg, int existingMsgPolicy) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0); + } + + private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0); + } + + private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0); + } + + private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0); + } + + private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) { + sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0); + } + + private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) { + sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0); + } + + private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, + int delay) { + if (existingMsgPolicy == SENDMSG_REPLACE) { + mBrokerHandler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) { + return; + } + + if (isMessageHandledUnderWakelock(msg)) { + final long identity = Binder.clearCallingIdentity(); + try { + mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS); + } catch (Exception e) { + Log.e(TAG, "Exception acquiring wakelock", e); + } + Binder.restoreCallingIdentity(identity); + } + + synchronized (sLastDeviceConnectionMsgTimeLock) { + long time = SystemClock.uptimeMillis() + delay; + + switch (msg) { + case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: + case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: + case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: + case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: + case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: + if (sLastDeviceConnectMsgTime >= time) { + // add a little delay to make sure messages are ordered as expected + time = sLastDeviceConnectMsgTime + 30; + } + sLastDeviceConnectMsgTime = time; + break; + default: + break; + } + + mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj), + time); + } + } + + //------------------------------------------------------------- + // internal utilities + private void sendBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java new file mode 100644 index 000000000000..eb76e6e02edc --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -0,0 +1,1024 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioDevicePort; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPort; +import android.media.AudioRoutesInfo; +import android.media.AudioSystem; +import android.media.IAudioRoutesObserver; +import android.os.Binder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; + +/** + * Class to manage the inventory of all connected devices. + * This class is thread-safe. + */ +public final class AudioDeviceInventory { + + private static final String TAG = "AS.AudioDeviceInventory"; + + // Actual list of connected devices + // Key for map created from DeviceInfo.makeDeviceListKey() + private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>(); + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // cache of the address of the last dock the device was connected to + private String mDockAddress; + + // Monitoring of audio routes. Protected by mAudioRoutes. + final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); + final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = + new RemoteCallbackList<IAudioRoutesObserver>(); + + //------------------------------------------------------------ + /** + * Class to store info about connected devices. + * Use makeDeviceListKey() to make a unique key for this list. + */ + private static class DeviceInfo { + final int mDeviceType; + final String mDeviceName; + final String mDeviceAddress; + int mDeviceCodecFormat; + + DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { + mDeviceType = deviceType; + mDeviceName = deviceName; + mDeviceAddress = deviceAddress; + mDeviceCodecFormat = deviceCodecFormat; + } + + @Override + public String toString() { + return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) + + " name:" + mDeviceName + + " addr:" + mDeviceAddress + + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; + } + + /** + * Generate a unique key for the mConnectedDevices List by composing the device "type" + * and the "address" associated with a specific instance of that device type + */ + private static String makeDeviceListKey(int device, String deviceAddress) { + return "0x" + Integer.toHexString(device) + ":" + deviceAddress; + } + } + + /** + * A class just for packaging up a set of connection parameters. + */ + /*package*/ class WiredDeviceConnectionState { + public final int mType; + public final @AudioService.ConnectionState int mState; + public final String mAddress; + public final String mName; + public final String mCaller; + + /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + mType = type; + mState = state; + mAddress = address; + mName = name; + mCaller = caller; + } + } + + //------------------------------------------------------------ + // Message handling from AudioDeviceBroker + + /** + * Restore previously connected devices. Use in case of audio server crash + * (see AudioService.onAudioServerDied() method) + */ + /*package*/ void onRestoreDevices() { + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceInfo di = mConnectedDevices.valueAt(i); + AudioSystem.setDeviceConnectionState( + di.mDeviceType, + AudioSystem.DEVICE_STATE_AVAILABLE, + di.mDeviceAddress, + di.mDeviceName, + di.mDeviceCodecFormat); + } + } + } + + /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, + @AudioService.BtProfileConnectionState int state, int a2dpVolume) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state=" + + state + " is dock=" + btDevice.isBluetoothDock()); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2DP sink connected: device addr=" + address + " state=" + state)); + + final int a2dpCodec; + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + a2dpCodec = btInfo.getCodec(); + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address, + AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + mDeviceBroker.cancelA2dpDockTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) { + mDeviceBroker.cancelA2dpDockTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress, + AudioSystem.AUDIO_FORMAT_DEFAULT); + } + } + if (a2dpVolume != -1) { + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onSetA2dpSinkConnectionState"); + mDeviceBroker.setDeviceVolume( + streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + makeA2dpDeviceAvailable(address, btDevice.getName(), + "onSetA2dpSinkConnectionState", a2dpCodec); + } + } + } + + /*package*/ void onSetA2dpSourceConnectionState( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + + state); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcAvailable(address); + } + } + } + + /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice, + @AudioService.BtProfileConnectionState int state) { + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onSetHearingAidConnectionState addr=" + address)); + + synchronized (mConnectedDevices) { + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, + btDevice.getAddress()); + final DeviceInfo di = mConnectedDevices.get(key); + boolean isConnected = di != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceAvailable(address, btDevice.getName(), + "onSetHearingAidConnectionState"); + } + } + } + + /*package*/ void onBluetoothA2dpDeviceConfigChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpDeviceConfigChange addr=" + address)); + + final int a2dpCodec = btInfo.getCodec(); + + synchronized (mConnectedDevices) { + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); + return; + } + // Device is connected + if (di.mDeviceCodecFormat != a2dpCodec) { + di.mDeviceCodecFormat = a2dpCodec; + mConnectedDevices.replace(key, di); + } + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" + + di.mDeviceCodecFormat); + } + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) { + // force A2DP device disconnection in case of error so that AudioService state + // is consistent with audio policy manager state + final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onBluetoothA2dpActiveDeviceChange( + @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) { + final BluetoothDevice btDevice = btInfo.getBtDevice(); + int a2dpVolume = btInfo.getVolume(); + final int a2dpCodec = btInfo.getCodec(); + + if (AudioService.DEBUG_DEVICES) { + Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "onBluetoothA2dpActiveDeviceChange addr=" + address)); + + synchronized (mConnectedDevices) { + //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo + // for this type of message + if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "A2dp config change ignored")); + return; + } + final String key = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + return; + } + + // Device is connected + if (a2dpVolume != -1) { + final AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + // Convert index to internal representation in VolumeStreamState + a2dpVolume = a2dpVolume * 10; + streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + "onBluetoothA2dpActiveDeviceChange"); + mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + + if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, + btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { + int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + // force A2DP device disconnection in case of error so that AudioService state is + // consistent with audio policy manager state + setBluetoothA2dpDeviceConnectionState( + btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, + false /* suppressNoisyIntent */, musicDevice, + -1 /* a2dpVolume */); + } + } + } + + /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow(address, a2dpCodec); + } + } + + /*package*/ void onReportNewRoutes() { + int n = mRoutesObservers.beginBroadcast(); + if (n > 0) { + AudioRoutesInfo routes; + synchronized (mCurAudioRoutes) { + routes = new AudioRoutesInfo(mCurAudioRoutes); + } + while (n > 0) { + n--; + IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); + try { + obs.dispatchAudioRoutesChanged(routes); + } catch (RemoteException e) { } + } + } + mRoutesObservers.finishBroadcast(); + mDeviceBroker.observeDevicesForAllStreams(); + } + + private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB; + + /*package*/ void onSetWiredDeviceConnectionState( + AudioDeviceInventory.WiredDeviceConnectionState wdcs) { + AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); + + synchronized (mConnectedDevices) { + if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) + && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { + mDeviceBroker.setBluetoothA2dpOnInt(true, + "onSetWiredDeviceConnectionState state DISCONNECTED"); + } + + if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress, + wdcs.mName)) { + // change of connection state failed, bailout + return; + } + if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { + if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { + mDeviceBroker.setBluetoothA2dpOnInt(false, + "onSetWiredDeviceConnectionState state not DISCONNECTED"); + } + mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller); + } + mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); + sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName); + updateAudioRoutes(wdcs.mType, wdcs.mState); + } + } + + /*package*/ void onToggleHdmi() { + synchronized (mConnectedDevices) { + // Is HDMI connected? + final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); + final DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); + return; + } + // Toggle HDMI to retrigger broadcast with proper formats. + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", + "android"); // disconnect + setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_STATE_AVAILABLE, "", "", + "android"); // reconnect + } + } + //------------------------------------------------------------ + // + + /** + * Implements the communication with AudioSystem to (dis)connect a device in the native layers + * @param connect true if connection + * @param device the device type + * @param address the address of the device + * @param deviceName human-readable name of device + * @return false if an error was reported by AudioSystem + */ + /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + + Integer.toHexString(device) + " address:" + address + + " name:" + deviceName + ")"); + } + synchronized (mConnectedDevices) { + final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceKey:" + deviceKey); + } + DeviceInfo di = mConnectedDevices.get(deviceKey); + boolean isConnected = di != null; + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); + } + if (connect && !isConnected) { + final int res = AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + + " due to command error " + res); + return false; + } + mConnectedDevices.put(deviceKey, new DeviceInfo( + device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(device); + return true; + } else if (!connect && isConnected) { + AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + // always remove even if disconnection failed + mConnectedDevices.remove(deviceKey); + return true; + } + Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + + ", deviceSpec=" + di + ", connect=" + connect); + } + return false; + } + + + /*package*/ void disconnectA2dp() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + makeA2dpDeviceUnavailableLater(deviceAddress, delay) + ); + } + } + } + } + + /*package*/ void disconnectA2dpSink() { + synchronized (mConnectedDevices) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); + } + } + + /*package*/ void disconnectHearingAid() { + synchronized (mConnectedDevices) { + synchronized (mDeviceBroker.mHearingAidLock) { + final ArraySet<String> toRemove = new ArraySet<>(); + // Disconnect ALL DEVICE_OUT_HEARING_AID devices + mConnectedDevices.values().forEach(deviceInfo -> { + if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + toRemove.add(deviceInfo.mDeviceAddress); + } + }); + if (toRemove.size() > 0) { + final int delay = checkSendBecomingNoisyIntentInt( + AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); + toRemove.stream().forEach(deviceAddress -> + // TODO delay not used? + makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) + ); + } + } + } + } + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { + synchronized (mConnectedDevices) { + return checkSendBecomingNoisyIntentInt(device, state, musicDevice); + } + } + + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + synchronized (mCurAudioRoutes) { + AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); + mRoutesObservers.register(observer); + return routes; + } + } + + /*package*/ AudioRoutesInfo getCurAudioRoutes() { + return mCurAudioRoutes; + } + + /*package*/ int setBluetoothA2dpDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { + int delay; + if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { + throw new IllegalArgumentException("invalid profile " + profile); + } + synchronized (mConnectedDevices) { + if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { + int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + intState, musicDevice); + } else { + delay = 0; + } + + final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device + + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec + + " suppressNoisyIntent: " + suppressNoisyIntent); + } + + final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo = + new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec); + if (profile == BluetoothProfile.A2DP) { + mDeviceBroker.postA2dpSinkConnection(state, + a2dpDeviceInfo, + delay); + } else { //profile == BluetoothProfile.A2DP_SINK + mDeviceBroker.postA2dpSourceConnection(state, + a2dpDeviceInfo, + delay); + } + } + return delay; + } + + /*package*/ int handleBluetoothA2dpActiveDeviceChange( + @NonNull BluetoothDevice device, + @AudioService.BtProfileConnectionState int state, int profile, + boolean suppressNoisyIntent, int a2dpVolume) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + return setBluetoothA2dpDeviceConnectionState(device, state, profile, + suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume); + } + // state == BluetoothProfile.STATE_CONNECTED + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i); + if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + continue; + } + // If A2DP device exists, this is either an active device change or + // device config change + final String existingDevicekey = mConnectedDevices.keyAt(i); + final String deviceName = device.getName(); + final String address = device.getAddress(); + final String newDeviceKey = DeviceInfo.makeDeviceListKey( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + int a2dpCodec = mDeviceBroker.getA2dpCodec(device); + // Device not equal to existing device, active device change + if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { + mConnectedDevices.remove(existingDevicekey); + mConnectedDevices.put(newDeviceKey, new DeviceInfo( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, + address, a2dpCodec)); + mDeviceBroker.postA2dpActiveDeviceChange( + new BtHelper.BluetoothA2dpDeviceInfo( + device, a2dpVolume, a2dpCodec)); + return 0; + } else { + // Device config change for existing device + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); + return 0; + } + } + } + return 0; + } + + /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, + String address, String name, String caller) { + synchronized (mConnectedDevices) { + int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); + mDeviceBroker.postSetWiredDeviceConnectionState( + new WiredDeviceConnectionState(type, state, address, name, caller), + delay); + return delay; + } + } + + /*package*/ int setBluetoothHearingAidDeviceConnectionState( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { + int delay; + synchronized (mConnectedDevices) { + if (!suppressNoisyIntent) { + int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID, + intState, musicDevice); + } else { + delay = 0; + } + mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); + return delay; + } + } + + + //------------------------------------------------------------------- + // Internal utilities + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceAvailable(String address, String name, String eventSource, + int a2dpCodec) { + // enable A2DP before notifying A2DP connection to avoid unnecessary processing in + // audio policy manager + AudioService.VolumeStreamState streamState = + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC); + mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); + // Reset A2DP suspend state each time a new sink is connected + AudioSystem.setParameters("A2dpSuspended=false"); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, + address, a2dpCodec)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { + if (address == null) { + return; + } + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + // Remove A2DP routes as well + setCurrentAudioRouteNameIfPossible(null); + if (mDockAddress == address) { + mDockAddress = null; + } + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { + // prevent any activity on the A2DP audio output to avoid unwanted + // reconnection of the sink. + AudioSystem.setParameters("A2dpSuspended=true"); + // retrieve DeviceInfo before removing device + final String deviceKey = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); + final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : + AudioSystem.AUDIO_FORMAT_DEFAULT; + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(deviceKey); + // send the delayed message to make the device unavailable later + mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs); + } + + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcAvailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + } + + @GuardedBy("mConnectedDevices") + private void makeA2dpSrcUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { + final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC) + .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC); + + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name, + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.put( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), + new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, + address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); + mDeviceBroker.setDeviceVolume( + mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC), + AudioSystem.DEVICE_OUT_HEARING_AID); + setCurrentAudioRouteNameIfPossible(name); + } + + @GuardedBy("mConnectedDevices") + private void makeHearingAidDeviceUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + AudioSystem.AUDIO_FORMAT_DEFAULT); + mConnectedDevices.remove( + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); + // Remove Hearing Aid routes as well + setCurrentAudioRouteNameIfPossible(null); + } + + @GuardedBy("mConnectedDevices") + private void setCurrentAudioRouteNameIfPossible(String name) { + synchronized (mCurAudioRoutes) { + if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { + return; + } + if (name != null || !isCurrentDeviceConnected()) { + mCurAudioRoutes.bluetoothName = name; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + @GuardedBy("mConnectedDevices") + private boolean isCurrentDeviceConnected() { + return mConnectedDevices.values().stream().anyMatch(deviceInfo -> + TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); + } + + // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only + // sent if: + // - none of these devices are connected anymore after one is disconnected AND + // - the device being disconnected is actually used for music. + // Access synchronized on mConnectedDevices + private int mBecomingNoisyIntentDevices = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI + | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE + | AudioSystem.DEVICE_OUT_HEARING_AID; + + // must be called before removing the device from mConnectedDevices + // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying + // from AudioSystem + @GuardedBy("mConnectedDevices") + private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) { + if (state != 0) { + return 0; + } + if ((device & mBecomingNoisyIntentDevices) == 0) { + return 0; + } + int delay = 0; + int devices = 0; + for (int i = 0; i < mConnectedDevices.size(); i++) { + int dev = mConnectedDevices.valueAt(i).mDeviceType; + if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) + && ((dev & mBecomingNoisyIntentDevices) != 0)) { + devices |= dev; + } + } + if (musicDevice == AudioSystem.DEVICE_NONE) { + musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + } + // ignore condition on device being actually used for music when in communication + // because music routing is altered in this case. + // also checks whether media routing if affected by a dynamic policy + if (((device == musicDevice) || mDeviceBroker.isInCommunication()) + && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) { + mDeviceBroker.broadcastBecomingNoisy(); + delay = 1000; + } + + return delay; + } + + // Intent "extra" data keys. + private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; + private static final String CONNECT_INTENT_KEY_STATE = "state"; + private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; + private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; + private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; + private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; + private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; + + private void sendDeviceConnectionIntent(int device, int state, String address, + String deviceName) { + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + + " state:0x" + Integer.toHexString(state) + " address:" + address + + " name:" + deviceName + ");"); + } + Intent intent = new Intent(); + + switch(device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 0); + break; + case AudioSystem.DEVICE_OUT_USB_HEADSET: + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", + AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); + break; + case AudioSystem.DEVICE_IN_USB_HEADSET: + if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") + == AudioSystem.DEVICE_STATE_AVAILABLE) { + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + } else { + // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing + return; + } + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + configureHdmiPlugIntent(intent, state); + break; + } + + if (intent.getAction() == null) { + return; + } + + intent.putExtra(CONNECT_INTENT_KEY_STATE, state); + intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); + intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); + + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateAudioRoutes(int device, int state) { + int connType = 0; + + switch (device) { + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + connType = AudioRoutesInfo.MAIN_HEADSET; + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + case AudioSystem.DEVICE_OUT_LINE: + connType = AudioRoutesInfo.MAIN_HEADPHONES; + break; + case AudioSystem.DEVICE_OUT_HDMI: + case AudioSystem.DEVICE_OUT_HDMI_ARC: + connType = AudioRoutesInfo.MAIN_HDMI; + break; + case AudioSystem.DEVICE_OUT_USB_DEVICE: + case AudioSystem.DEVICE_OUT_USB_HEADSET: + connType = AudioRoutesInfo.MAIN_USB; + break; + } + + synchronized (mCurAudioRoutes) { + if (connType == 0) { + return; + } + int newConn = mCurAudioRoutes.mainType; + if (state != 0) { + newConn |= connType; + } else { + newConn &= ~connType; + } + if (newConn != mCurAudioRoutes.mainType) { + mCurAudioRoutes.mainType = newConn; + mDeviceBroker.postReportNewRoutes(); + } + } + } + + private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { + intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); + intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); + if (state != AudioService.CONNECTION_STATE_CONNECTED) { + return; + } + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + int[] portGeneration = new int[1]; + int status = AudioSystem.listAudioPorts(ports, portGeneration); + if (status != AudioManager.SUCCESS) { + Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); + return; + } + for (AudioPort port : ports) { + if (!(port instanceof AudioDevicePort)) { + continue; + } + final AudioDevicePort devicePort = (AudioDevicePort) port; + if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI + && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) { + continue; + } + // found an HDMI port: format the list of supported encodings + int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); + if (formats.length > 0) { + ArrayList<Integer> encodingList = new ArrayList(1); + for (int format : formats) { + // a format in the list can be 0, skip it + if (format != AudioFormat.ENCODING_INVALID) { + encodingList.add(format); + } + } + final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); + intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); + } + // find the maximum supported number of channels + int maxChannels = 0; + for (int mask : devicePort.channelMasks()) { + int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); + if (channelCount > maxChannels) { + maxChannels = channelCount; + } + } + intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index de389bc3aa01..df33bf249133 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -35,14 +36,9 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IUidObserver; import android.app.NotificationManager; -import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothCodecConfig; -import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -65,14 +61,12 @@ import android.hardware.hdmi.HdmiPlaybackClient; import android.hardware.hdmi.HdmiTvClient; import android.hardware.usb.UsbManager; import android.media.AudioAttributes; -import android.media.AudioDevicePort; import android.media.AudioFocusInfo; import android.media.AudioFocusRequest; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPlaybackConfiguration; -import android.media.AudioPort; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; @@ -104,7 +98,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -120,8 +113,6 @@ import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; @@ -137,10 +128,8 @@ import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.audio.AudioServiceEvents.ForceUseEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; -import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent; import com.android.server.pm.UserManagerService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -150,6 +139,8 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; @@ -175,19 +166,20 @@ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, AccessibilityManager.AccessibilityServicesStateChangeListener { - private static final String TAG = "AudioService"; + private static final String TAG = "AS.AudioService"; /** Debug audio mode */ - protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG); + protected static final boolean DEBUG_MODE = false; /** Debug audio policy feature */ - protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG); + protected static final boolean DEBUG_AP = false; /** Debug volumes */ - protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG); + protected static final boolean DEBUG_VOL = false; /** debug calls to devices APIs */ - protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG); + protected static final boolean DEBUG_DEVICES = false; + /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -213,11 +205,11 @@ public class AudioService extends IAudioService.Stub return mPlatformType == AudioSystem.PLATFORM_VOICE; } - private boolean isPlatformTelevision() { + /*package*/ boolean isPlatformTelevision() { return mPlatformType == AudioSystem.PLATFORM_TELEVISION; } - private boolean isPlatformAutomotive() { + /*package*/ boolean isPlatformAutomotive() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } @@ -242,52 +234,40 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_FORCE_USE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; private static final int MSG_SET_ALL_VOLUMES = 10; - private static final int MSG_REPORT_NEW_ROUTES = 12; - private static final int MSG_SET_FORCE_BT_A2DP_USE = 13; - private static final int MSG_CHECK_MUSIC_ACTIVE = 14; - private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18; - private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19; - private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; - private static final int MSG_SYSTEM_READY = 21; - private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; - private static final int MSG_UNMUTE_STREAM = 24; - private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25; - private static final int MSG_INDICATE_SYSTEM_READY = 26; - private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27; - private static final int MSG_NOTIFY_VOL_EVENT = 28; - private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29; - private static final int MSG_ENABLE_SURROUND_FORMATS = 30; + private static final int MSG_CHECK_MUSIC_ACTIVE = 11; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13; + private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14; + private static final int MSG_UNLOAD_SOUND_EFFECTS = 15; + private static final int MSG_SYSTEM_READY = 16; + private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17; + private static final int MSG_UNMUTE_STREAM = 18; + private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19; + private static final int MSG_INDICATE_SYSTEM_READY = 20; + private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21; + private static final int MSG_NOTIFY_VOL_EVENT = 22; + private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23; + private static final int MSG_ENABLE_SURROUND_FORMATS = 24; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100; - private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101; - private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102; - private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103; - private static final int MSG_DISABLE_AUDIO_FOR_UID = 104; - private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105; - private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106; - private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107; + private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; // end of messages handled under wakelock - private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; - // Timeout for connection to bluetooth headset service - private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; - // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; - private static final int BT_HEARING_AID_GAIN_MIN = -128; - /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ private AudioHandler mAudioHandler; /** @see VolumeStreamState */ private VolumeStreamState[] mStreamStates; + + /*package*/ VolumeStreamState getStreamState(int stream) { + return mStreamStates[stream]; + } + private SettingsObserver mSettingsObserver; private int mMode = AudioSystem.MODE_NORMAL; @@ -477,135 +457,13 @@ public class AudioService extends IAudioService.Stub private final UserRestrictionsListener mUserRestrictionsListener = new AudioServiceUserRestrictionsListener(); - // Devices currently connected - // Use makeDeviceListKey() to make a unique key for this list. - private class DeviceListSpec { - int mDeviceType; - String mDeviceName; - String mDeviceAddress; - int mDeviceCodecFormat; - - DeviceListSpec(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat) { - mDeviceType = deviceType; - mDeviceName = deviceName; - mDeviceAddress = deviceAddress; - mDeviceCodecFormat = deviceCodecFormat; - } - - public String toString() { - return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName - + " address:" + mDeviceAddress - + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; - } - } - - // Generate a unique key for the mConnectedDevices List by composing the device "type" - // and the "address" associated with a specific instance of that device type - private String makeDeviceListKey(int device, String deviceAddress) { - return "0x" + Integer.toHexString(device) + ":" + deviceAddress; - } - - private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>(); - - private class BluetoothA2dpDeviceInfo { - BluetoothDevice mBtDevice; - int mVolume; - int mCodec; - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) { - this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); - } - - BluetoothA2dpDeviceInfo(BluetoothDevice btDevice, - int volume, int codec) { - mBtDevice = btDevice; - mVolume = volume; - mCodec = codec; - } - - public BluetoothDevice getBtDevice() { - return mBtDevice; - } - - public int getVolume() { - return mVolume; - } - - public int getCodec() { - return mCodec; - } - } - - private int mapBluetoothCodecToAudioFormat(int btCodecType) { - switch (btCodecType) { - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: - return AudioSystem.AUDIO_FORMAT_SBC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: - return AudioSystem.AUDIO_FORMAT_AAC; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: - return AudioSystem.AUDIO_FORMAT_APTX; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: - return AudioSystem.AUDIO_FORMAT_APTX_HD; - case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: - return AudioSystem.AUDIO_FORMAT_LDAC; - default: - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - } - - // Forced device usage for communications - private int mForcedUseForComm; - private int mForcedUseForCommExt; // External state returned by getters: always consistent - // with requests by setters - // List of binder death handlers for setMode() client processes. // The last process to have called setMode() is at the top of the list. - private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); - - // List of clients having issued a SCO start request - private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); - - // BluetoothHeadset API to control SCO connection - private BluetoothHeadset mBluetoothHeadset; - - // Bluetooth headset device - private BluetoothDevice mBluetoothHeadsetDevice; - - // Indicate if SCO audio connection is currently active and if the initiator is - // audio service (internal) or bluetooth headset (external) - private int mScoAudioState; - // SCO audio state is not active - private static final int SCO_STATE_INACTIVE = 0; - // SCO audio activation request waiting for headset service to connect - private static final int SCO_STATE_ACTIVATE_REQ = 1; - // SCO audio state is active or starting due to a request from AudioManager API - private static final int SCO_STATE_ACTIVE_INTERNAL = 3; - // SCO audio deactivation request waiting for headset service to connect - private static final int SCO_STATE_DEACTIVATE_REQ = 4; - // SCO audio deactivation in progress, waiting for Bluetooth audio intent - private static final int SCO_STATE_DEACTIVATING = 5; - - // SCO audio state is active due to an action in BT handsfree (either voice recognition or - // in call audio) - private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; - - // Indicates the mode used for SCO audio connection. The mode is virtual call if the request - // originated from an app targeting an API version before JB MR2 and raw audio after that. - private int mScoAudioMode; - // SCO audio mode is undefined - private static final int SCO_MODE_UNDEFINED = -1; - // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) - private static final int SCO_MODE_VIRTUAL_CALL = 0; - // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) - private static final int SCO_MODE_RAW = 1; - // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) - private static final int SCO_MODE_VR = 2; - - private static final int SCO_MODE_MAX = 2; - - // Current connection state indicated by bluetooth headset - private int mScoConnectionState; + // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers + //TODO candidate to be moved to separate class that handles synchronization + @GuardedBy("mDeviceBroker.mSetModeLock") + /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers = + new ArrayList<SetModeDeathHandler>(); // true if boot sequence has been completed private boolean mSystemReady; @@ -636,15 +494,6 @@ public class AudioService extends IAudioService.Stub // Used to play ringtones outside system_server private volatile IRingtonePlayer mRingtonePlayer; - // Request to override default use of A2DP for media. - private boolean mBluetoothA2dpEnabled; - private final Object mBluetoothA2dpEnabledLock = new Object(); - - // Monitoring of audio routes. Protected by mCurAudioRoutes. - final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); - final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers - = new RemoteCallbackList<IAudioRoutesObserver>(); - // Devices for which the volume is fixed and VolumePanel slider should be disabled int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | @@ -669,17 +518,6 @@ public class AudioService extends IAudioService.Stub private final MediaFocusControl mMediaFocusControl; - // Reference to BluetoothA2dp to query for volume. - private BluetoothHearingAid mHearingAid; - // lock always taken synchronized on mConnectedDevices - private final Object mHearingAidLock = new Object(); - // Reference to BluetoothA2dp to query for AbsoluteVolume. - private BluetoothA2dp mA2dp; - // lock always taken synchronized on mConnectedDevices - private final Object mA2dpAvrcpLock = new Object(); - // If absolute volume is supported in AVRCP device - private boolean mAvrcpAbsVolSupported = false; - // Pre-scale for Bluetooth Absolute Volume private float[] mPrescaleAbsoluteVolume = new float[] { 0.5f, // Pre-scale for index 1 @@ -687,8 +525,6 @@ public class AudioService extends IAudioService.Stub 0.85f, // Pre-scale for index 3 }; - private static Long mLastDeviceConnectMsgTime = new Long(0); - private NotificationManager mNm; private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; @@ -705,15 +541,6 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private int mAssistantUid; - // Intent "extra" data keys. - public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; - public static final String CONNECT_INTENT_KEY_STATE = "state"; - public static final String CONNECT_INTENT_KEY_ADDRESS = "address"; - public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; - public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; - public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; - public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; - // Defines the format for the connection "address" for ALSA devices public static String makeAlsaAddressString(int card, int device) { return "card=" + card + ";device=" + device + ";"; @@ -858,8 +685,6 @@ public class AudioService extends IAudioService.Stub sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); - mForcedUseForComm = AudioSystem.FORCE_NONE; - createAudioSystemThread(); AudioSystem.setErrorCallback(mAudioSystemCallback); @@ -886,6 +711,8 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + mDeviceBroker = new AudioDeviceBroker(mContext, this); + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] // array initialized by updateStreamVolumeAlias() updateStreamVolumeAlias(false /*updateVolumes*/, TAG); @@ -988,23 +815,7 @@ public class AudioService extends IAudioService.Stub sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); - mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR; - resetBluetoothSco(); - getBluetoothHeadset(); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - sendStickyBroadcastToAll(newIntent); - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.A2DP); - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEARING_AID); - } + mDeviceBroker.onSystemReady(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { synchronized (mHdmiClientLock) { @@ -1065,39 +876,22 @@ public class AudioService extends IAudioService.Stub readAndSetLowRamDevice(); - // Restore device connection states - synchronized (mConnectedDevices) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec spec = mConnectedDevices.valueAt(i); - AudioSystem.setDeviceConnectionState( - spec.mDeviceType, - AudioSystem.DEVICE_STATE_AVAILABLE, - spec.mDeviceAddress, - spec.mDeviceName, - spec.mDeviceCodecFormat); - } - } + // Restore device connection states, BT state + mDeviceBroker.onAudioServerDied(); + // Restore call state if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) { mModeLogger.log(new AudioEventLogger.StringEvent( "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")")); } - // Restore forced usage for communications and record - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm); - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm); final int forSys; synchronized (mSettingsLock) { forSys = mCameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE; } - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys); + + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied"); // Restore stream volumes int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1120,20 +914,10 @@ public class AudioService extends IAudioService.Stub RotationHelper.updateOrientation(); } - synchronized (mBluetoothA2dpEnabledLock) { - final int forMed = mBluetoothA2dpEnabled ? - AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed); - } - synchronized (mSettingsLock) { final int forDock = mDockAudioMediaEnabled ? AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock, - "onAudioServerDied")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); updateAssistantUId(true); @@ -1209,6 +993,45 @@ public class AudioService extends IAudioService.Stub } } + /** + * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected. + */ + /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) { + if (state != 0) { + // DEVICE_OUT_HDMI is now connected + if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + + if (isPlatformTelevision()) { + mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; + checkAllFixedVolumeDevices(); + synchronized (mHdmiClientLock) { + if (mHdmiManager != null && mHdmiPlaybackClient != null) { + mHdmiCecSink = false; + mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); + } + } + } + sendEnabledSurroundFormats(mContentResolver, true); + } else { + // DEVICE_OUT_HDMI disconnected + if (isPlatformTelevision()) { + synchronized (mHdmiClientLock) { + if (mHdmiManager != null) { + mHdmiCecSink = false; + } + } + } + } + } + private void checkAllFixedVolumeDevices() { int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -1373,7 +1196,7 @@ public class AudioService extends IAudioService.Stub private void sendEncodedSurroundMode(ContentResolver cr, String eventSource) { - int encodedSurroundMode = Settings.Global.getInt( + final int encodedSurroundMode = Settings.Global.getInt( cr, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); sendEncodedSurroundMode(encodedSurroundMode, eventSource); @@ -1402,13 +1225,8 @@ public class AudioService extends IAudioService.Stub break; } if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) { - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_ENCODED_SURROUND, - forceSetting, - eventSource, - 0); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting, + eventSource); } } @@ -1632,7 +1450,7 @@ public class AudioService extends IAudioService.Stub + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream + ", userSelect=" + mUserSelectedVolumeControlStream); - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append("/").append(caller).append(" uid:").append(uid).toString())); final int streamType; @@ -1690,7 +1508,7 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -1871,16 +1689,18 @@ public class AudioService extends IAudioService.Stub if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(newIndex / 10); - } + if (DEBUG_VOL) { + Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + + newIndex + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex); } // Check if volume update should be send to Hearing Aid if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(newIndex, streamType); + Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); } // Check if volume update should be sent to Hdmi system audio. @@ -2052,7 +1872,7 @@ public class AudioService extends IAudioService.Stub + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } - mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); @@ -2127,18 +1947,20 @@ public class AudioService extends IAudioService.Stub index = rescaleIndex(index * 10, streamType, streamTypeAlias); - if (streamTypeAlias == AudioSystem.STREAM_MUSIC && - (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(index / 10); - } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC + && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 + && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index + + "stream=" + streamType); } + mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { - setHearingAidVolume(index, streamType); + Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index + + " stream=" + streamType); + mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { @@ -2881,6 +2703,10 @@ public class AudioService extends IAudioService.Stub } } + /*package*/ void setUpdateRingerModeServiceInt() { + setRingerModeInt(getRingerModeInternal(), false); + } + /** @see AudioManager#shouldVibrate(int) */ public boolean shouldVibrate(int vibrateType) { if (!mHasVibrator) return false; @@ -2921,7 +2747,7 @@ public class AudioService extends IAudioService.Stub } - private class SetModeDeathHandler implements IBinder.DeathRecipient { + /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mPid; private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client @@ -2934,7 +2760,7 @@ public class AudioService extends IAudioService.Stub public void binderDied() { int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); @@ -2949,9 +2775,7 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - final long ident = Binder.clearCallingIdentity(); - disconnectBluetoothSco(newModeOwnerPid); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } @@ -2994,7 +2818,7 @@ public class AudioService extends IAudioService.Stub int oldModeOwnerPid = 0; int newModeOwnerPid = 0; - synchronized(mSetModeDeathHandlers) { + synchronized (mDeviceBroker.mSetModeLock) { if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } @@ -3006,11 +2830,11 @@ public class AudioService extends IAudioService.Stub // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { - disconnectBluetoothSco(newModeOwnerPid); + mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } } - // must be called synchronized on mSetModeDeathHandlers + // must be called synchronized on mSetModeLock // setModeInt() returns a valid PID if the audio mode was successfully set to // any mode other than NORMAL. private int setModeInt(int mode, IBinder cb, int pid, String caller) { @@ -3380,26 +3204,12 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - if (on) { - if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, - eventSource, 0); - } - mForcedUseForComm = AudioSystem.FORCE_SPEAKER; - } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){ - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - - mForcedUseForCommExt = mForcedUseForComm; - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); + mDeviceBroker.setSpeakerphoneOn(on, eventSource); } /** @see AudioManager#isSpeakerphoneOn() */ public boolean isSpeakerphoneOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER); + return mDeviceBroker.isSpeakerphoneOn(); } /** @see AudioManager#setBluetoothScoOn(boolean) */ @@ -3410,7 +3220,7 @@ public class AudioService extends IAudioService.Stub // Only enable calls from system components if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) { - mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + mDeviceBroker.setBluetoothScoOnByApp(on); return; } @@ -3418,95 +3228,57 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - setBluetoothScoOnInt(on, eventSource); - } - - public void setBluetoothScoOnInt(boolean on, String eventSource) { - Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); - if (on) { - // do not accept SCO ON if SCO audio is not connected - synchronized (mScoClients) { - if ((mBluetoothHeadset != null) - && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { - mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; - Log.w(TAG, "setBluetoothScoOnInt(true) failed because " - + mBluetoothHeadsetDevice + " is not in audio connected mode"); - return; - } - } - mForcedUseForComm = AudioSystem.FORCE_BT_SCO; - } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { - mForcedUseForComm = AudioSystem.FORCE_NONE; - } - mForcedUseForCommExt = mForcedUseForComm; - AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off")); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0); - sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, - AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0); - // Un-mute ringtone stream volume - setRingerModeInt(getRingerModeInternal(), false); + + mDeviceBroker.setBluetoothScoOn(on, eventSource); } - /** @see AudioManager#isBluetoothScoOn() */ + /** @see AudioManager#isBluetoothScoOn() + * Note that it doesn't report internal state, but state seen by apps (which may have + * called setBluetoothScoOn() */ public boolean isBluetoothScoOn() { - return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO); + return mDeviceBroker.isBluetoothScoOnForApp(); } + // TODO investigate internal users due to deprecation of SDK API /** @see AudioManager#setBluetoothA2dpOn(boolean) */ public void setBluetoothA2dpOn(boolean on) { // for logging only final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); - - synchronized (mBluetoothA2dpEnabledLock) { - if (mBluetoothA2dpEnabled == on) { - return; - } - mBluetoothA2dpEnabled = on; - sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource, 0); - } + mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource); } /** @see AudioManager#isBluetoothA2dpOn() */ public boolean isBluetoothA2dpOn() { - synchronized (mBluetoothA2dpEnabledLock) { - return mBluetoothA2dpEnabled; - } + return mDeviceBroker.isBluetoothA2dpOn(); } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb, int targetSdkVersion) { - int scoAudioMode = + final int scoAudioMode = (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ? - SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED; - startBluetoothScoInt(cb, scoAudioMode); + BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED; + final String eventSource = new StringBuilder("startBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, scoAudioMode, eventSource); } /** @see AudioManager#startBluetoothScoVirtualCall() */ public void startBluetoothScoVirtualCall(IBinder cb) { - startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL); + final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int scoAudioMode){ + void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) { if (!checkAudioSettingsPermission("startBluetoothSco()") || !mSystemReady) { return; } - ScoClient client = getScoClient(cb, true); - // The calling identity must be cleared before calling ScoClient.incCount(). - // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - // The caller identity must be cleared after getScoClient() because it is needed if a new - // client is created. - final long ident = Binder.clearCallingIdentity(); - client.incCount(scoAudioMode); - Binder.restoreCallingIdentity(ident); + mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource); } /** @see AudioManager#stopBluetoothSco() */ @@ -3515,648 +3287,15 @@ public class AudioService extends IAudioService.Stub !mSystemReady) { return; } - ScoClient client = getScoClient(cb, false); - // The calling identity must be cleared before calling ScoClient.decCount(). - // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs - // and this must be done on behalf of system server to make sure permissions are granted. - final long ident = Binder.clearCallingIdentity(); - if (client != null) { - client.decCount(); - } - Binder.restoreCallingIdentity(ident); - } - - - private class ScoClient implements IBinder.DeathRecipient { - private IBinder mCb; // To be notified of client's death - private int mCreatorPid; - private int mStartcount; // number of SCO connections started by this client - - ScoClient(IBinder cb) { - mCb = cb; - mCreatorPid = Binder.getCallingPid(); - mStartcount = 0; - } - - public void binderDied() { - synchronized(mScoClients) { - Log.w(TAG, "SCO client died"); - int index = mScoClients.indexOf(this); - if (index < 0) { - Log.w(TAG, "unregistered SCO client died"); - } else { - clearCount(true); - mScoClients.remove(this); - } - } - } - - public void incCount(int scoAudioMode) { - synchronized(mScoClients) { - requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); - if (mStartcount == 0) { - try { - mCb.linkToDeath(this, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); - } - } - mStartcount++; - } - } - - public void decCount() { - synchronized(mScoClients) { - if (mStartcount == 0) { - Log.w(TAG, "ScoClient.decCount() already 0"); - } else { - mStartcount--; - if (mStartcount == 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "decCount() going to 0 but not registered to binder"); - } - } - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public void clearCount(boolean stopSco) { - synchronized(mScoClients) { - if (mStartcount != 0) { - try { - mCb.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder"); - } - } - mStartcount = 0; - if (stopSco) { - requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); - } - } - } - - public int getCount() { - return mStartcount; - } - - public IBinder getBinder() { - return mCb; - } - - public int getPid() { - return mCreatorPid; - } - - public int totalCount() { - synchronized(mScoClients) { - int count = 0; - for (ScoClient mScoClient : mScoClients) { - count += mScoClient.getCount(); - } - return count; - } - } - - private void requestScoState(int state, int scoAudioMode) { - checkScoAudioState(); - int clientCount = totalCount(); - if (clientCount != 0) { - Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode - + ", clientCount=" + clientCount); - return; - } - if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - // Make sure that the state transitions to CONNECTING even if we cannot initiate - // the connection. - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); - // Accept SCO audio activation only in NORMAL audio mode or if the mode is - // currently controlled by the same client process. - synchronized(mSetModeDeathHandlers) { - int modeOwnerPid = mSetModeDeathHandlers.isEmpty() - ? 0 : mSetModeDeathHandlers.get(0).getPid(); - if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { - Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " - + modeOwnerPid + " != creatorPid " + mCreatorPid); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - return; - } - switch (mScoAudioState) { - case SCO_STATE_INACTIVE: - mScoAudioMode = scoAudioMode; - if (scoAudioMode == SCO_MODE_UNDEFINED) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - if (mBluetoothHeadsetDevice != null) { - mScoAudioMode = Settings.Global.getInt(mContentResolver, - "bluetooth_sco_channel_" - + mBluetoothHeadsetDevice.getAddress(), - SCO_MODE_VIRTUAL_CALL); - if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - } - } - } - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " connection, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - Log.w(TAG, "requestScoState: no active device while connecting," - + " mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } else { - Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice - + " failed, mScoAudioMode=" + mScoAudioMode); - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_DEACTIVATING: - mScoAudioState = SCO_STATE_ACTIVATE_REQ; - break; - case SCO_STATE_DEACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to connect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - - } - } - } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVE_INTERNAL: - if (mBluetoothHeadset == null) { - if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_DEACTIVATE_REQ; - } else { - Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" - + " disconnection, mScoAudioMode=" + mScoAudioMode); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - } - if (mBluetoothHeadsetDevice == null) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } else { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - break; - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - default: - Log.w(TAG, "requestScoState: failed to disconnect in state " - + mScoAudioState + ", scoAudioMode=" + scoAudioMode); - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - break; - } - } - } - } - - private void checkScoAudioState() { - synchronized (mScoClients) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && - mScoAudioState == SCO_STATE_INACTIVE && - mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - } - } - - - private ScoClient getScoClient(IBinder cb, boolean create) { - synchronized(mScoClients) { - for (ScoClient existingClient : mScoClients) { - if (existingClient.getBinder() == cb) { - return existingClient; - } - } - if (create) { - ScoClient newClient = new ScoClient(cb); - mScoClients.add(newClient); - return newClient; - } - return null; - } - } - - public void clearAllScoClients(int exceptPid, boolean stopSco) { - synchronized(mScoClients) { - ScoClient savedClient = null; - for (ScoClient cl : mScoClients) { - if (cl.getPid() != exceptPid) { - cl.clearCount(stopSco); - } else { - savedClient = cl; - } - } - mScoClients.clear(); - if (savedClient != null) { - mScoClients.add(savedClient); - } - } - } - - private boolean getBluetoothHeadset() { - boolean result = false; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - } - // If we could not get a bluetooth headset proxy, send a failure message - // without delay to reset the SCO audio state and clear SCO clients. - // If we could get a proxy, send a delayed failure message that will reset our state - // in case we don't receive onServiceConnected(). - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, - SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0); - return result; - } - - /** - * Disconnect all SCO connections started by {@link AudioManager} except those started by - * {@param exceptPid} - * - * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept - */ - private void disconnectBluetoothSco(int exceptPid) { - synchronized(mScoClients) { - checkScoAudioState(); - if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { - return; - } - clearAllScoClients(exceptPid, true); - } - } - - private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.disconnectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.stopVoiceRecognition(device); - default: - return false; - } - } - - private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, - BluetoothDevice device, int scoAudioMode) { - switch (scoAudioMode) { - case SCO_MODE_RAW: - return bluetoothHeadset.connectAudio(); - case SCO_MODE_VIRTUAL_CALL: - return bluetoothHeadset.startScoUsingVirtualVoiceCall(); - case SCO_MODE_VR: - return bluetoothHeadset.startVoiceRecognition(device); - default: - return false; - } - } - - private void resetBluetoothSco() { - synchronized(mScoClients) { - clearAllScoClients(0, false); - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - AudioSystem.setParameters("A2dpSuspended=false"); - setBluetoothScoOnInt(false, "resetBluetoothSco"); - } - - private void broadcastScoConnectionState(int state) { - sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE, - SENDMSG_QUEUE, state, 0, null, 0); - } - - private void onBroadcastScoConnectionState(int state) { - if (state != mScoConnectionState) { - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, - mScoConnectionState); - sendStickyBroadcastToAll(newIntent); - mScoConnectionState = state; - } - } - - private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { - if (btDevice == null) { - return true; - } - String address = btDevice.getAddress(); - BluetoothClass btClass = btDevice.getBluetoothClass(); - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - int[] outDeviceTypes = { - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, - AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; - if (btClass != null) { - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; - break; - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; - break; - } - } - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - String btDeviceName = btDevice.getName(); - boolean result = false; - if (isActive) { - result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName); - } else { - for (int outDeviceType : outDeviceTypes) { - result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName); - } - } - // handleDeviceConnection() && result to make sure the method get executed - result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result; - return result; - } - - private void setBtScoActiveDevice(BluetoothDevice btDevice) { - synchronized (mScoClients) { - Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); - final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; - if (!Objects.equals(btDevice, previousActiveDevice)) { - if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { - Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " - + previousActiveDevice); - } - if (!handleBtScoActiveDeviceChange(btDevice, true)) { - Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); - // set mBluetoothHeadsetDevice to null when failing to add new device - btDevice = null; - } - mBluetoothHeadsetDevice = btDevice; - if (mBluetoothHeadsetDevice == null) { - resetBluetoothSco(); - } - } - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - BluetoothDevice btDevice; - List<BluetoothDevice> deviceList; - switch(profile) { - case BluetoothProfile.A2DP: - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - mA2dp = (BluetoothA2dp) proxy; - deviceList = mA2dp.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mA2dp.getConnectionState(btDevice); - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, - AudioSystem.DEVICE_NONE); - final String addr = btDevice == null ? "null" : btDevice.getAddress(); - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2DP service connected: device addr=" + addr - + " state=" + state)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SINK_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - delay); - } - } - } - break; - - case BluetoothProfile.A2DP_SINK: - deviceList = proxy.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - synchronized (mConnectedDevices) { - int state = proxy.getConnectionState(btDevice); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_SRC_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(btDevice), - 0 /* delay */); - } - } - break; - - case BluetoothProfile.HEADSET: - synchronized (mScoClients) { - // Discard timeout message - mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); - mBluetoothHeadset = (BluetoothHeadset) proxy; - setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); - // Refresh SCO audio state - checkScoAudioState(); - // Continue pending action if any - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { - boolean status = false; - if (mBluetoothHeadsetDevice != null) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - status = connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } - break; - case SCO_STATE_DEACTIVATE_REQ: - status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode); - if (status) { - mScoAudioState = SCO_STATE_DEACTIVATING; - } - break; - } - } - if (!status) { - mScoAudioState = SCO_STATE_INACTIVE; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - } - } - break; - - case BluetoothProfile.HEARING_AID: - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - mHearingAid = (BluetoothHearingAid) proxy; - deviceList = mHearingAid.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - int state = mHearingAid.getConnectionState(btDevice); - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_HEARING_AID, intState, - AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - btDevice, - delay); - } - } - } - - break; - - default: - break; - } - } - public void onServiceDisconnected(int profile) { - - switch (profile) { - case BluetoothProfile.A2DP: - disconnectA2dp(); - break; - - case BluetoothProfile.A2DP_SINK: - disconnectA2dpSink(); - break; - - case BluetoothProfile.HEADSET: - disconnectHeadset(); - break; - - case BluetoothProfile.HEARING_AID: - disconnectHearingAid(); - break; - - default: - break; - } - } - }; - - void disconnectAllBluetoothProfiles() { - disconnectA2dp(); - disconnectA2dpSink(); - disconnectHeadset(); - disconnectHearingAid(); - } - - void disconnectA2dp() { - synchronized (mConnectedDevices) { - synchronized (mA2dpAvrcpLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay); - } - } - } - } - } - - void disconnectA2dpSink() { - synchronized (mConnectedDevices) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices - for(int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - for (int i = 0; i < toRemove.size(); i++) { - makeA2dpSrcUnavailable(toRemove.valueAt(i)); - } - } - } + final String eventSource = new StringBuilder("stopBluetoothSco()") + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource); } - void disconnectHeadset() { - synchronized (mScoClients) { - setBtScoActiveDevice(null); - mBluetoothHeadset = null; - } - } - void disconnectHearingAid() { - synchronized (mConnectedDevices) { - synchronized (mHearingAidLock) { - ArraySet<String> toRemove = null; - // Disconnect ALL DEVICE_OUT_HEARING_AID devices - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - toRemove = toRemove != null ? toRemove : new ArraySet<String>(); - toRemove.add(deviceSpec.mDeviceAddress); - } - } - if (toRemove != null) { - int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - 0, AudioSystem.DEVICE_NONE); - for (int i = 0; i < toRemove.size(); i++) { - makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/); - } - } - } - } + /*package*/ ContentResolver getContentResolver() { + return mContentResolver; } private void onCheckMusicActive(String caller) { @@ -4173,8 +3312,8 @@ public class AudioService extends IAudioService.Stub caller, MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); - if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && - (index > safeMediaVolumeIndex(device))) { + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) + && (index > safeMediaVolumeIndex(device))) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { @@ -4192,8 +3331,7 @@ public class AudioService extends IAudioService.Stub mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } - private int getSafeUsbMediaVolumeIndex() - { + private int getSafeUsbMediaVolumeIndex() { // determine UI volume index corresponding to the wanted safe gain in dBFS int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; @@ -4201,7 +3339,7 @@ public class AudioService extends IAudioService.Stub mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f; - while (Math.abs(max-min) > 1) { + while (Math.abs(max - min) > 1) { int index = (max + min) / 2; float gainDB = AudioSystem.getStreamVolumeDB( AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); @@ -4518,7 +3656,7 @@ public class AudioService extends IAudioService.Stub || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } - private boolean isInCommunication() { + /*package*/ boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = @@ -4671,25 +3809,9 @@ public class AudioService extends IAudioService.Stub } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { return; } - synchronized (mLastDeviceConnectMsgTime) { - long time = SystemClock.uptimeMillis() + delay; - - if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || - msg == MSG_SET_A2DP_SINK_CONNECTION_STATE || - msg == MSG_SET_HEARING_AID_CONNECTION_STATE || - msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || - msg == MSG_A2DP_DEVICE_CONFIG_CHANGE || - msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE || - msg == MSG_BTA2DP_DOCK_TIMEOUT) { - if (mLastDeviceConnectMsgTime >= time) { - // add a little delay to make sure messages are ordered as expected - time = mLastDeviceConnectMsgTime + 30; - } - mLastDeviceConnectMsgTime = time; - } - handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); - } + final long time = SystemClock.uptimeMillis() + delay; + handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } boolean checkAudioSettingsPermission(String method) { @@ -4704,7 +3826,7 @@ public class AudioService extends IAudioService.Stub return false; } - private int getDeviceForStream(int stream) { + /*package*/ int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: @@ -4749,160 +3871,94 @@ public class AudioService extends IAudioService.Stub } } - private int getA2dpCodec(BluetoothDevice device) { - synchronized (mA2dpAvrcpLock) { - if (mA2dp == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); - if (btCodecStatus == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); - if (btCodecConfig == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; - } - return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); - } + + /*package*/ void observeDevicesForAllStreams() { + observeDevicesForStreams(-1); } - /* - * A class just for packaging up a set of connection parameters. + /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0; + /*package*/ static final int CONNECTION_STATE_CONNECTED = 1; + /** + * The states that can be used with AudioService.setWiredDeviceConnectionState() + * Attention: those values differ from those in BluetoothProfile, follow annotations to + * distinguish between @ConnectionState and @BtProfileConnectionState */ - class WiredDeviceConnectionState { - public final int mType; - public final int mState; - public final String mAddress; - public final String mName; - public final String mCaller; + @IntDef({ + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectionState {} - public WiredDeviceConnectionState(int type, int state, String address, String name, - String caller) { - mType = type; - mState = state; - mAddress = address; - mName = name; - mCaller = caller; - } - } - - public void setWiredDeviceConnectionState(int type, int state, String address, String name, + /** + * see AudioManager.setWiredDeviceConnectionState() + */ + public void setWiredDeviceConnectionState(int type, + @ConnectionState int state, String address, String name, String caller) { - synchronized (mConnectedDevices) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:" - + address + ")"); - } - int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_WIRED_DEVICE_CONNECTION_STATE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new WiredDeviceConnectionState(type, state, address, name, caller), - delay); + if (state != CONNECTION_STATE_CONNECTED + && state != CONNECTION_STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } + mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); } + /** + * @hide + * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState() + * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + @IntDef({ + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BtProfileConnectionState {} + public int setBluetoothHearingAidDeviceConnectionState( - BluetoothDevice device, int state, boolean suppressNoisyIntent, - int musicDevice) + @NonNull BluetoothDevice device, @BtProfileConnectionState int state, + boolean suppressNoisyIntent, int musicDevice) { - int delay; - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setHearingAidDeviceConnectionState state=" + state - + " addr=" + device.getAddress() - + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG)); - synchronized (mConnectedDevices) { - if (!suppressNoisyIntent) { - int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, - intState, musicDevice); - } else { - delay = 0; - } - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_HEARING_AID_CONNECTION_STATE, - state, - 0 /* arg2 unused */, - device, - delay); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - return delay; - } - - public int setBluetoothA2dpDeviceConnectionState( - BluetoothDevice device, int state, int profile) - { - return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, false /* suppressNoisyIntent */, - -1 /* a2dpVolume */); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); + } + return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + device, state, suppressNoisyIntent, musicDevice, "AudioService"); } - public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device, - int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) - { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state - // only querying address as this is the only readily available field on the device - + " addr=" + device.getAddress() - + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent - + " vol=" + a2dpVolume)).printLog(TAG)); - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored")); - return 0; - } - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); - } - - public int setBluetoothA2dpDeviceConnectionStateInt( - BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, - int musicDevice, int a2dpVolume) - { - int delay; - if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { - throw new IllegalArgumentException("invalid profile " + profile); + /** + * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() + */ + public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( + @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, + int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } - synchronized (mConnectedDevices) { - if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { - int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; - delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - intState, musicDevice); - } else { - delay = 0; - } - - int a2dpCodec = getA2dpCodec(device); - - if (DEBUG_DEVICES) { - Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device - + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec - + " suppressNoisyIntent: " + suppressNoisyIntent); - } - - queueMsgUnderWakeLock(mAudioHandler, - (profile == BluetoothProfile.A2DP ? - MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE), - state, - 0, /* arg2 unused */ - new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec), - delay); + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Illegal BluetoothProfile state for device " + + " (dis)connection, got " + state); } - return delay; + return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state, + profile, suppressNoisyIntent, a2dpVolume); } + /** + * See AudioManager.handleBluetoothA2dpDeviceConfigChange() + * @param device + */ public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) { - synchronized (mConnectedDevices) { - int a2dpCodec = getA2dpCodec(device); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_DEVICE_CONFIG_CHANGE, - 0 /* arg1 unused */, - 0 /* arg2 unused */, - new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec), - 0 /* delay */); + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); } + mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } /** @@ -4912,52 +3968,18 @@ public class AudioService extends IAudioService.Stub public int handleBluetoothA2dpActiveDeviceChange( BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { + if (device == null) { + throw new IllegalArgumentException("Illegal null device"); + } if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { throw new IllegalArgumentException("invalid profile " + profile); } - - synchronized (mConnectedDevices) { - if (state == BluetoothA2dp.STATE_CONNECTED) { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - continue; - } - // If A2DP device exists, this is either an active device change or - // device config change - String existingDevicekey = mConnectedDevices.keyAt(i); - String deviceName = device.getName(); - String address = device.getAddress(); - String newDeviceKey = makeDeviceListKey( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - int a2dpCodec = getA2dpCodec(device); - // Device not equal to existing device, active device change - if (!TextUtils.equals(existingDevicekey, newDeviceKey)) { - mConnectedDevices.remove(existingDevicekey); - mConnectedDevices.put(newDeviceKey, new DeviceListSpec( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, - address, a2dpCodec)); - queueMsgUnderWakeLock(mAudioHandler, - MSG_A2DP_ACTIVE_DEVICE_CHANGE, - 0, - 0, - new BluetoothA2dpDeviceInfo( - device, a2dpVolume, a2dpCodec), - 0 /* delay */); - return 0; - } else { - // Device config change for existing device - handleBluetoothA2dpDeviceConfigChange(device); - return 0; - } - } - } + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + throw new IllegalArgumentException("Invalid state " + state); } - - // New device connection or a device disconnect - return setBluetoothA2dpDeviceConnectionStateInt( - device, state, profile, suppressNoisyIntent, - AudioSystem.DEVICE_NONE, a2dpVolume); + return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile, + suppressNoisyIntent, a2dpVolume); } private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = @@ -4967,24 +3989,27 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; + /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { + sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, + newDevice, 0, null, 0); + } + private void onAccessoryPlugMediaUnmute(int newDevice) { if (DEBUG_VOL) { Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } - synchronized (mConnectedDevices) { - if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS - && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 - && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted - && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 - && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) - { - if (DEBUG_VOL) { - Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", - newDevice, AudioSystem.getOutputDeviceName(newDevice))); - } - mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); + + if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0 + && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted + && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0 + && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) { + if (DEBUG_VOL) { + Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]", + newDevice, AudioSystem.getOutputDeviceName(newDevice))); } + mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); } } @@ -4994,7 +4019,7 @@ public class AudioService extends IAudioService.Stub // NOTE: Locking order for synchronized objects related to volume or ringer mode management: // 1 mScoclient OR mSafeMediaVolumeState - // 2 mSetModeDeathHandlers + // 2 mSetModeLock // 3 mSettingsLock // 4 VolumeStreamState.class public class VolumeStreamState { @@ -5138,11 +4163,11 @@ public class AudioService extends IAudioService.Stub } // must be called while synchronized VolumeStreamState.class - public void applyDeviceVolume_syncVSS(int device) { + /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) { int index; if (mIsMuted) { index = 0; - } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) { + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5151,10 +4176,11 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(device) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } public void applyAllVolumes() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // apply device specific volumes first int index; @@ -5164,7 +4190,7 @@ public class AudioService extends IAudioService.Stub if (mIsMuted) { index = 0; } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && - mAvrcpAbsVolSupported) { + isAvrcpAbsVolSupported) { index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if ((device & mFullVolumeDevices) != 0) { index = (mIndexMax + 5)/10; @@ -5173,7 +4199,7 @@ public class AudioService extends IAudioService.Stub } else { index = (mIndexMap.valueAt(i) + 5)/10; } - AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } } // apply default volume last: by convention , default device volume will be used @@ -5183,7 +4209,7 @@ public class AudioService extends IAudioService.Stub } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; } - AudioSystem.setStreamVolumeIndex( + AudioSystem.setStreamVolumeIndexAS( mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); } } @@ -5373,6 +4399,7 @@ public class AudioService extends IAudioService.Stub } public void checkFixedVolumeDevices() { + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // ignore settings for fixed volume devices: volume should always be at max or 0 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) { @@ -5383,7 +4410,7 @@ public class AudioService extends IAudioService.Stub || (((device & mFixedVolumeDevices) != 0) && index != 0)) { mIndexMap.put(device, mIndexMax); } - applyDeviceVolume_syncVSS(device); + applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); } } } @@ -5465,11 +4492,13 @@ public class AudioService extends IAudioService.Stub } } - private void setDeviceVolume(VolumeStreamState streamState, int device) { + /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { + + final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported(); synchronized (VolumeStreamState.class) { // Apply volume - streamState.applyDeviceVolume_syncVSS(device); + streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); @@ -5479,11 +4508,13 @@ public class AudioService extends IAudioService.Stub // Make sure volume is also maxed out on A2DP device for aliased stream // that may have a different device selected int streamDevice = getDeviceForStream(streamType); - if ((device != streamDevice) && mAvrcpAbsVolSupported && - ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { - mStreamStates[streamType].applyDeviceVolume_syncVSS(device); + if ((device != streamDevice) && isAvrcpAbsVolSupported + && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume_syncVSS(device, + isAvrcpAbsVolSupported); } - mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice); + mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice, + isAvrcpAbsVolSupported); } } } @@ -5762,12 +4793,6 @@ public class AudioService extends IAudioService.Stub } } - private void setForceUse(int usage, int config, String eventSource) { - synchronized (mConnectedDevices) { - setForceUseInt_SyncDevices(usage, config, eventSource); - } - } - private void onPersistSafeVolumeState(int state) { Settings.Global.putInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, @@ -5834,56 +4859,20 @@ public class AudioService extends IAudioService.Stub onPlaySoundEffect(msg.arg1, msg.arg2); break; - case MSG_BTA2DP_DOCK_TIMEOUT: - // msg.obj == address of BTA2DP device - synchronized (mConnectedDevices) { - makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); - } - mAudioEventWakeLock.release(); - break; - case MSG_SET_FORCE_USE: - case MSG_SET_FORCE_BT_A2DP_USE: - setForceUse(msg.arg1, msg.arg2, (String) msg.obj); - break; - - case MSG_BT_HEADSET_CNCT_FAILED: - resetBluetoothSco(); - break; - - case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: - { WiredDeviceConnectionState connectState = - (WiredDeviceConnectionState)msg.obj; - mDeviceLogger.log(new WiredDevConnectEvent(connectState)); - onSetWiredDeviceConnectionState(connectState.mType, connectState.mState, - connectState.mAddress, connectState.mName, connectState.mCaller); - mAudioEventWakeLock.release(); + { + final String eventSource = (String) msg.obj; + final int useCase = msg.arg1; + final int config = msg.arg2; + if (useCase == AudioSystem.FOR_MEDIA) { + Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from " + + eventSource); + break; } - break; - - case MSG_SET_A2DP_SRC_CONNECTION_STATE: - onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_A2DP_SINK_CONNECTION_STATE: - onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_SET_HEARING_AID_CONNECTION_STATE: - onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_DEVICE_CONFIG_CHANGE: - onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); - break; - - case MSG_A2DP_ACTIVE_DEVICE_CHANGE: - onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj); - mAudioEventWakeLock.release(); + sForceUseLogger.log( + new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); + AudioSystem.setForceUse(useCase, config); + } break; case MSG_DISABLE_AUDIO_FOR_UID: @@ -5892,35 +4881,10 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; - case MSG_REPORT_NEW_ROUTES: { - int N = mRoutesObservers.beginBroadcast(); - if (N > 0) { - AudioRoutesInfo routes; - synchronized (mCurAudioRoutes) { - routes = new AudioRoutesInfo(mCurAudioRoutes); - } - while (N > 0) { - N--; - IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N); - try { - obs.dispatchAudioRoutesChanged(routes); - } catch (RemoteException e) { - } - } - } - mRoutesObservers.finishBroadcast(); - observeDevicesForStreams(-1); - break; - } - case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; - case MSG_BROADCAST_AUDIO_BECOMING_NOISY: - onSendBecomingNoisyIntent(); - break; - case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), @@ -5930,10 +4894,6 @@ public class AudioService extends IAudioService.Stub onPersistSafeVolumeState(msg.arg1); break; - case MSG_BROADCAST_BT_CONNECTION_STATE: - onBroadcastScoConnectionState(msg.arg1); - break; - case MSG_SYSTEM_READY: onSystemReady(); break; @@ -6033,20 +4993,7 @@ public class AudioService extends IAudioService.Stub if (mEncodedSurroundMode != newSurroundMode) { // Send to AudioPolicyManager sendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); - synchronized(mConnectedDevices) { - // Is HDMI connected? - String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); - DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec != null) { - // Toggle HDMI to retrigger broadcast with proper formats. - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", - "android"); // disconnect - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_AVAILABLE, "", "", - "android"); // reconnect - } - } + mDeviceBroker.toggleHdmiIfConnected_Async(); mEncodedSurroundMode = newSurroundMode; mSurroundModeChanged = true; } else { @@ -6055,515 +5002,18 @@ public class AudioService extends IAudioService.Stub } } - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceAvailable( - String address, String name, String eventSource, int a2dpCodec) { - // enable A2DP before notifying A2DP connection to avoid unnecessary processing in - // audio policy manager - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - setBluetoothA2dpOnInt(true, eventSource); - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); - // Reset A2DP suspend state each time a new sink is connected - AudioSystem.setParameters("A2dpSuspended=false"); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, a2dpCodec)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0); - setCurrentAudioRouteNameIfPossible(name); - } - - private void onSendBecomingNoisyIntent() { - mDeviceLogger.log((new AudioEventLogger.StringEvent( - "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); - sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { - if (address == null) { - return; - } - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = false; - } - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); - // Remove A2DP routes as well - setCurrentAudioRouteNameIfPossible(null); - if (mDockAddress == address) { - mDockAddress = null; - } - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { - // prevent any activity on the A2DP audio output to avoid unwanted - // reconnection of the sink. - AudioSystem.setParameters("A2dpSuspended=true"); - // Retrieve deviceSpec before removing device - String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat : - AudioSystem.AUDIO_FORMAT_DEFAULT; - // the device will be made unavailable later, so consider it disconnected right away - mConnectedDevices.remove(deviceKey); - // send the delayed message to make the device unavailable later - queueMsgUnderWakeLock(mAudioHandler, - MSG_BTA2DP_DOCK_TIMEOUT, - a2dpCodec, - 0, - address, - delayMs); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcAvailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), - new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - } - - // must be called synchronized on mConnectedDevices - private void makeA2dpSrcUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); - } - - private void setHearingAidVolume(int index, int streamType) { - synchronized (mHearingAidLock) { - if (mHearingAid != null) { - //hearing aid expect volume value in range -128dB to 0dB - int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10, - AudioSystem.DEVICE_OUT_HEARING_AID); - if (gainDB < BT_HEARING_AID_GAIN_MIN) - gainDB = BT_HEARING_AID_GAIN_MIN; - mHearingAid.setVolume(gainDB); - } - } - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { - int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID); - setHearingAidVolume(index, AudioSystem.STREAM_MUSIC); - - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.put( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name, - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0); - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_HEARING_AID, 0, - mStreamStates[AudioSystem.STREAM_MUSIC], 0); - setCurrentAudioRouteNameIfPossible(name); - } - - // must be called synchronized on mConnectedDevices - private void makeHearingAidDeviceUnavailable(String address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", - AudioSystem.AUDIO_FORMAT_DEFAULT); - mConnectedDevices.remove( - makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); - // Remove Hearing Aid routes as well - setCurrentAudioRouteNameIfPossible(null); - } - - // must be called synchronized on mConnectedDevices - private void cancelA2dpDeviceTimeout() { - mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - // must be called synchronized on mConnectedDevices - private boolean hasScheduledA2dpDockTimeout() { - return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); - } - - private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state - + " is dock: " + btDevice.isBluetoothDock()); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat); - } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - cancelA2dpDeviceTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if (hasScheduledA2dpDockTimeout() && mDockAddress != null) { - cancelA2dpDeviceTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress, - AudioSystem.AUDIO_FORMAT_DEFAULT); - } - } - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - "onSetA2dpSinkConnectionState"); - setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); - } - makeA2dpDeviceAvailable(address, btDevice.getName(), - "onSetA2dpSinkConnectionState", a2dpCodec); - } - } - } - - private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - - if (DEBUG_VOL) { - Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeA2dpSrcAvailable(address); - } - } - } - - private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state) - { - if (DEBUG_DEVICES) { - Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state); - } - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - - synchronized (mConnectedDevices) { - final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, - btDevice.getAddress()); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - boolean isConnected = deviceSpec != null; - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceUnavailable(address); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceAvailable(address, btDevice.getName(), - "onSetHearingAidConnectionState"); - } - } - } - - private void setCurrentAudioRouteNameIfPossible(String name) { - synchronized (mCurAudioRoutes) { - if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { - if (name != null || !isCurrentDeviceConnected()) { - mCurAudioRoutes.bluetoothName = name; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private boolean isCurrentDeviceConnected() { - for (int i = 0; i < mConnectedDevices.size(); i++) { - DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); - if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) { - return true; - } - } - return false; - } - - private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo) - { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpDeviceConfigChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - // Device is connected - if (deviceSpec.mDeviceCodecFormat != a2dpCodec) { - deviceSpec.mDeviceCodecFormat = a2dpCodec; - mConnectedDevices.replace(key, deviceSpec); - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec=" - + deviceSpec.mDeviceCodecFormat); - } - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), deviceSpec.mDeviceCodecFormat) - != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - - /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */ - public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) { - if (btInfo == null) { - return; - } - BluetoothDevice btDevice = btInfo.getBtDevice(); - int a2dpVolume = btInfo.getVolume(); - int a2dpCodec = btInfo.getCodec(); - - if (btDevice == null) { - return; - } - if (DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice); - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "onBluetoothA2dpActiveDeviceChange addr=" + address)); - - int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - synchronized (mConnectedDevices) { - if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( - "A2dp config change ignored")); - return; - } - final String key = makeDeviceListKey(device, address); - final DeviceListSpec deviceSpec = mConnectedDevices.get(key); - if (deviceSpec == null) { - return; - } - - // Device is connected - if (a2dpVolume != -1) { - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - // Convert index to internal representation in VolumeStreamState - a2dpVolume = a2dpVolume * 10; - streamState.setIndex(a2dpVolume, device, - "onBluetoothA2dpActiveDeviceChange"); - setDeviceVolume(streamState, device); - } - - if (AudioSystem.handleDeviceConfigChange(device, address, - btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { - int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothA2dpDeviceConnectionStateInt( - btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP, - false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */); - } - } - } - public void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported - synchronized (mA2dpAvrcpLock) { - mAvrcpAbsVolSupported = support; - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); - } - } - - private boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) - + " address:" + address + " name:" + deviceName + ")"); - } - synchronized (mConnectedDevices) { - String deviceKey = makeDeviceListKey(device, address); - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceKey:" + deviceKey); - } - DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); - boolean isConnected = deviceSpec != null; - if (DEBUG_DEVICES) { - Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected); - } - if (connect && !isConnected) { - final int res = AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - if (res != AudioSystem.AUDIO_STATUS_OK) { - Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) + - " due to command error " + res ); - return false; - } - mConnectedDevices.put(deviceKey, new DeviceListSpec(device, - deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); - sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, - device, 0, null, 0); - return true; - } else if (!connect && isConnected) { - AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); - // always remove even if disconnection failed - mConnectedDevices.remove(deviceKey); - return true; - } - Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec=" - + deviceSpec + ", connect=" + connect); - } - return false; - } - - // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only - // sent if: - // - none of these devices are connected anymore after one is disconnected AND - // - the device being disconnected is actually used for music. - // Access synchronized on mConnectedDevices - int mBecomingNoisyIntentDevices = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI | - AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | - AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_HEARING_AID; - - // must be called before removing the device from mConnectedDevices - // Called synchronized on mConnectedDevices - // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying - // from AudioSystem - private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) { - int delay = 0; - if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) { - int devices = 0; - for (int i = 0; i < mConnectedDevices.size(); i++) { - int dev = mConnectedDevices.valueAt(i).mDeviceType; - if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) - && ((dev & mBecomingNoisyIntentDevices) != 0)) { - devices |= dev; - } - } - if (musicDevice == AudioSystem.DEVICE_NONE) { - musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - } - // ignore condition on device being actually used for music when in communication - // because music routing is altered in this case. - // also checks whether media routing if affected by a dynamic policy - if (((device == musicDevice) || isInCommunication()) && (device == devices) - && !hasMediaDynamicPolicy()) { - mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY); - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); - delay = 1000; - } - } - - return delay; } /** * @return true if there is currently a registered dynamic mixing policy that affects media */ - private boolean hasMediaDynamicPolicy() { + /*package*/ boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; @@ -6578,213 +5028,25 @@ public class AudioService extends IAudioService.Stub } } - private void updateAudioRoutes(int device, int state) - { - int connType = 0; - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - connType = AudioRoutesInfo.MAIN_HEADSET; - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - connType = AudioRoutesInfo.MAIN_HEADPHONES; - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - connType = AudioRoutesInfo.MAIN_HDMI; - } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE|| - device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - connType = AudioRoutesInfo.MAIN_USB; - } - - synchronized (mCurAudioRoutes) { - if (connType != 0) { - int newConn = mCurAudioRoutes.mainType; - if (state != 0) { - newConn |= connType; - } else { - newConn &= ~connType; - } - if (newConn != mCurAudioRoutes.mainType) { - mCurAudioRoutes.mainType = newConn; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } - } - } - - private void sendDeviceConnectionIntent(int device, int state, String address, - String deviceName) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + - " state:0x" + Integer.toHexString(state) + " address:" + address + - " name:" + deviceName + ");"); - } - Intent intent = new Intent(); - - if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || - device == AudioSystem.DEVICE_OUT_LINE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 0); - } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", - AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); - } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) { - if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") - == AudioSystem.DEVICE_STATE_AVAILABLE) { - intent.setAction(Intent.ACTION_HEADSET_PLUG); - intent.putExtra("microphone", 1); - } else { - // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing - return; - } - } else if (device == AudioSystem.DEVICE_OUT_HDMI || - device == AudioSystem.DEVICE_OUT_HDMI_ARC) { - configureHdmiPlugIntent(intent, state); - } - - if (intent.getAction() == null) { - return; - } - - intent.putExtra(CONNECT_INTENT_KEY_STATE, state); - intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); - intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); - - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - - final long ident = Binder.clearCallingIdentity(); - try { - ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG = - AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_LINE | - AudioSystem.DEVICE_OUT_ALL_USB; - - private void onSetWiredDeviceConnectionState(int device, int state, String address, - String deviceName, String caller) { - if (DEBUG_DEVICES) { - Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) - + " state:" + Integer.toHexString(state) - + " address:" + address - + " deviceName:" + deviceName - + " caller: " + caller + ");"); - } - - synchronized (mConnectedDevices) { - if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) { - setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0"); - } - - if (!handleDeviceConnection(state == 1, device, address, deviceName)) { - // change of connection state failed, bailout - return; - } - if (state != 0) { - if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) { - setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0"); - } - if ((device & mSafeMediaVolumeDevices) != 0) { - sendMsg(mAudioHandler, - MSG_CHECK_MUSIC_ACTIVE, - SENDMSG_REPLACE, - 0, - 0, - caller, - MUSIC_ACTIVE_POLL_PERIOD_MS); - } - // Television devices without CEC service apply software volume on HDMI output - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; - checkAllFixedVolumeDevices(); - synchronized (mHdmiClientLock) { - if (mHdmiManager != null && mHdmiPlaybackClient != null) { - mHdmiCecSink = false; - mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); - } - } - } - if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) { - sendEnabledSurroundFormats(mContentResolver, true); - } - } else { - if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { - synchronized (mHdmiClientLock) { - if (mHdmiManager != null) { - mHdmiCecSink = false; - } - } - } - } - sendDeviceConnectionIntent(device, state, address, deviceName); - updateAudioRoutes(device, state); - } - } - - private void configureHdmiPlugIntent(Intent intent, int state) { - intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); - intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); - if (state == 1) { - ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); - int[] portGeneration = new int[1]; - int status = AudioSystem.listAudioPorts(ports, portGeneration); - if (status == AudioManager.SUCCESS) { - for (AudioPort port : ports) { - if (port instanceof AudioDevicePort) { - final AudioDevicePort devicePort = (AudioDevicePort) port; - if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI || - devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) { - // format the list of supported encodings - int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); - if (formats.length > 0) { - ArrayList<Integer> encodingList = new ArrayList(1); - for (int format : formats) { - // a format in the list can be 0, skip it - if (format != AudioFormat.ENCODING_INVALID) { - encodingList.add(format); - } - } - int[] encodingArray = new int[encodingList.size()]; - for (int i = 0 ; i < encodingArray.length ; i++) { - encodingArray[i] = encodingList.get(i); - } - intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); - } - // find the maximum supported number of channels - int maxChannels = 0; - for (int mask : devicePort.channelMasks()) { - int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); - if (channelCount > maxChannels) { - maxChannels = channelCount; - } - } - intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); - } - } - } - } + /*package*/ void checkMusicActive(int deviceType, String caller) { + if ((deviceType & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); } } - /* cache of the address of the last dock the device was connected to */ - private String mDockAddress; - /** * Receiver for misc intent broadcasts the Phone app cares about. */ private class AudioServiceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + final String action = intent.getAction(); int outDevice; int inDevice; int state; @@ -6812,75 +5074,16 @@ public class AudioService extends IAudioService.Stub } // Low end docks have a menu to enable or disable audio // (see mDockAudioMediaEnabled) - if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) && - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config, - "ACTION_DOCK_EVENT intent")); - AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); + if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) + || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) + && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config, + "ACTION_DOCK_EVENT intent"); } mDockState = dockState; - } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { - BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - setBtScoActiveDevice(btDevice); - } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { - boolean broadcast = false; - int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - synchronized (mScoClients) { - int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - // broadcast intent if the connection was initated by AudioService - if (!mScoClients.isEmpty() && - (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || - mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATING)) { - broadcast = true; - } - switch (btState) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - setBluetoothScoOn(true); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - setBluetoothScoOn(false); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // startBluetoothSco called after stopBluetoothSco - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcast = false; - break; - } - } - // Tear down SCO if disconnected from external - clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - default: - // do not broadcast CONNECTING or invalid state - broadcast = false; - break; - } - } - if (broadcast) { - broadcastScoConnectionState(scoAudioState); - //FIXME: this is to maintain compatibility with deprecated intent - // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. - Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); - newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); - sendStickyBroadcastToAll(newIntent); - } + } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED) + || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + mDeviceBroker.receiveBtEvent(intent); } else if (action.equals(Intent.ACTION_SCREEN_ON)) { if (mMonitorRotation) { RotationHelper.enable(); @@ -6898,13 +5101,7 @@ public class AudioService extends IAudioService.Stub if (mUserSwitchedReceived) { // attempt to stop music playback for background user except on first user // switch (i.e. first boot) - sendMsg(mAudioHandler, - MSG_BROADCAST_AUDIO_BECOMING_NOISY, - SENDMSG_REPLACE, - 0, - 0, - null, - 0); + mDeviceBroker.broadcastBecomingNoisy(); } mUserSwitchedReceived = true; // the current audio focus owner is no longer valid @@ -6938,7 +5135,7 @@ public class AudioService extends IAudioService.Stub state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) { - disconnectAllBluetoothProfiles(); + mDeviceBroker.disconnectAllBluetoothProfiles(); } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { @@ -7178,22 +5375,17 @@ public class AudioService extends IAudioService.Stub // take new state into account for streams muted by ringer mode setRingerModeInt(getRingerModeInternal(), false); } - - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_SYSTEM, + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, - new String("handleConfigurationChanged"), - 0); - + "handleConfigurationChanged"); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, 0, 0, mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); + } } mVolumeController.setLayoutDirection(config.getLayoutDirection()); @@ -7202,28 +5394,6 @@ public class AudioService extends IAudioService.Stub } } - // Handles request to override default use of A2DP for media. - // Must be called synchronized on mConnectedDevices - public void setBluetoothA2dpOnInt(boolean on, String eventSource) { - synchronized (mBluetoothA2dpEnabledLock) { - mBluetoothA2dpEnabled = on; - mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); - setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - eventSource); - } - } - - // Must be called synchronized on mConnectedDevices - private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) { - if (usage == AudioSystem.FOR_MEDIA) { - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource)); - AudioSystem.setForceUse(usage, config); - } - @Override public void setRingtonePlayer(IRingtonePlayer player) { mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null); @@ -7237,11 +5407,7 @@ public class AudioService extends IAudioService.Stub @Override public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { - synchronized (mCurAudioRoutes) { - AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); - mRoutesObservers.register(observer); - return routes; - } + return mDeviceBroker.startWatchingRoutes(observer); } @@ -7283,9 +5449,9 @@ public class AudioService extends IAudioService.Stub // the headset is compliant to EN 60950 with a max loudness of 100dB SPL. private int mSafeUsbMediaVolumeIndex; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, - private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | - AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | - AudioSystem.DEVICE_OUT_USB_HEADSET; + /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET + | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + | AudioSystem.DEVICE_OUT_USB_HEADSET; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. @@ -7438,9 +5604,8 @@ public class AudioService extends IAudioService.Stub mHdmiSystemAudioSupported = on; final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE; - mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, - config, "setHdmiSystemAudioSupported")); - AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config); + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config, + "setHdmiSystemAudioSupported"); } device = getDevicesForStream(AudioSystem.STREAM_MUSIC); } @@ -7553,14 +5718,14 @@ public class AudioService extends IAudioService.Stub // logs for wired + A2DP device connections: // - wired: logged before onSetWiredDeviceConnectionState() is executed // - A2DP: logged at reception of method call - final private AudioEventLogger mDeviceLogger = new AudioEventLogger( - LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection"); + /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger( + LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI"); - final private AudioEventLogger mForceUseLogger = new AudioEventLogger( + static final AudioEventLogger sForceUseLogger = new AudioEventLogger( LOG_NB_EVENTS_FORCE_USE, "force use (logged before setForceUse() is executed)"); - final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, + static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME, "volume changes (logged when command received by AudioService)"); final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY, @@ -7613,8 +5778,9 @@ public class AudioService extends IAudioService.Stub dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); - pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType)); - pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName); + pw.print(" mMainType=0x"); pw.println(Integer.toHexString( + mDeviceBroker.getCurAudioRoutes().mainType)); + pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName); pw.println("\nOther state:"); pw.print(" mVolumeController="); pw.println(mVolumeController); @@ -7630,7 +5796,8 @@ public class AudioService extends IAudioService.Stub pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); - pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); + pw.print(" mAvrcpAbsVolSupported="); + pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported()); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -7643,11 +5810,11 @@ public class AudioService extends IAudioService.Stub pw.println("\nEvent logs:"); mModeLogger.dump(pw); pw.println("\n"); - mDeviceLogger.dump(pw); + sDeviceLogger.dump(pw); pw.println("\n"); - mForceUseLogger.dump(pw); + sForceUseLogger.dump(pw); pw.println("\n"); - mVolumeLogger.dump(pw); + sVolumeLogger.dump(pw); } private static String safeMediaVolumeStateToString(int state) { @@ -8270,6 +6437,11 @@ public class AudioService extends IAudioService.Stub } //====================== + // Audio device management + //====================== + private final AudioDeviceBroker mDeviceBroker; + + //====================== // Audio policy proxy //====================== private static final class AudioDeviceArray { diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 9d9e35bdf2b2..7ccb45e97912 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -19,7 +19,7 @@ package com.android.server.audio; import android.media.AudioManager; import android.media.AudioSystem; -import com.android.server.audio.AudioService.WiredDeviceConnectionState; +import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState; public class AudioServiceEvents { @@ -89,9 +89,11 @@ public class AudioServiceEvents { } final static class VolumeEvent extends AudioEventLogger.Event { - final static int VOL_ADJUST_SUGG_VOL = 0; - final static int VOL_ADJUST_STREAM_VOL = 1; - final static int VOL_SET_STREAM_VOL = 2; + static final int VOL_ADJUST_SUGG_VOL = 0; + static final int VOL_ADJUST_STREAM_VOL = 1; + static final int VOL_SET_STREAM_VOL = 2; + static final int VOL_SET_HEARING_AID_VOL = 3; + static final int VOL_SET_AVRCP_VOL = 4; final int mOp; final int mStream; @@ -107,6 +109,24 @@ public class AudioServiceEvents { mCaller = caller; } + VolumeEvent(int op, int index, int gainDb) { + mOp = op; + mVal1 = index; + mVal2 = gainDb; + //unused + mStream = -1; + mCaller = null; + } + + VolumeEvent(int op, int index) { + mOp = op; + mVal1 = index; + //unused + mVal2 = 0; + mStream = -1; + mCaller = null; + } + @Override public String eventToString() { switch (mOp) { @@ -131,6 +151,15 @@ public class AudioServiceEvents { .append(" flags:0x").append(Integer.toHexString(mVal2)) .append(") from ").append(mCaller) .toString(); + case VOL_SET_HEARING_AID_VOL: + return new StringBuilder("setHearingAidVolume:") + .append(" index:").append(mVal1) + .append(" gain dB:").append(mVal2) + .toString(); + case VOL_SET_AVRCP_VOL: + return new StringBuilder("setAvrcpVolume:") + .append(" index:").append(mVal1) + .toString(); default: return new StringBuilder("FIXME invalid op:").append(mOp).toString(); } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java new file mode 100644 index 000000000000..bf325013c7da --- /dev/null +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -0,0 +1,949 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * @hide + * Class to encapsulate all communication with Bluetooth services + */ +public class BtHelper { + + private static final String TAG = "AS.BtHelper"; + + private final @NonNull AudioDeviceBroker mDeviceBroker; + + BtHelper(@NonNull AudioDeviceBroker broker) { + mDeviceBroker = broker; + } + + // List of clients having issued a SCO start request + private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>(); + + // BluetoothHeadset API to control SCO connection + private BluetoothHeadset mBluetoothHeadset; + + // Bluetooth headset device + private BluetoothDevice mBluetoothHeadsetDevice; + + // Indicate if SCO audio connection is currently active and if the initiator is + // audio service (internal) or bluetooth headset (external) + private int mScoAudioState; + // SCO audio state is not active + private static final int SCO_STATE_INACTIVE = 0; + // SCO audio activation request waiting for headset service to connect + private static final int SCO_STATE_ACTIVATE_REQ = 1; + // SCO audio state is active or starting due to a request from AudioManager API + private static final int SCO_STATE_ACTIVE_INTERNAL = 3; + // SCO audio deactivation request waiting for headset service to connect + private static final int SCO_STATE_DEACTIVATE_REQ = 4; + // SCO audio deactivation in progress, waiting for Bluetooth audio intent + private static final int SCO_STATE_DEACTIVATING = 5; + + // SCO audio state is active due to an action in BT handsfree (either voice recognition or + // in call audio) + private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; + + // Indicates the mode used for SCO audio connection. The mode is virtual call if the request + // originated from an app targeting an API version before JB MR2 and raw audio after that. + private int mScoAudioMode; + // SCO audio mode is undefined + /*package*/ static final int SCO_MODE_UNDEFINED = -1; + // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) + /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; + // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) + private static final int SCO_MODE_RAW = 1; + // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) + private static final int SCO_MODE_VR = 2; + + private static final int SCO_MODE_MAX = 2; + + // Current connection state indicated by bluetooth headset + private int mScoConnectionState; + + private static final int BT_HEARING_AID_GAIN_MIN = -128; + + @GuardedBy("mDeviceBroker.mHearingAidLock") + private BluetoothHearingAid mHearingAid; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private BluetoothA2dp mA2dp; + // If absolute volume is supported in AVRCP device + @GuardedBy("mDeviceBroker.mA2dpAvrcpLock") + private boolean mAvrcpAbsVolSupported = false; + + //---------------------------------------------------------------------- + /*package*/ static class BluetoothA2dpDeviceInfo { + private final @NonNull BluetoothDevice mBtDevice; + private final int mVolume; + private final int mCodec; + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { + this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); + } + + BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { + mBtDevice = btDevice; + mVolume = volume; + mCodec = codec; + } + + public @NonNull BluetoothDevice getBtDevice() { + return mBtDevice; + } + + public int getVolume() { + return mVolume; + } + + public int getCodec() { + return mCodec; + } + } + + //---------------------------------------------------------------------- + // Interface for AudioDeviceBroker + + /*package*/ void onSystemReady() { + mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; + resetBluetoothSco(); + getBluetoothHeadset(); + + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + sendStickyBroadcastToAll(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.A2DP); + adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID); + } + } + + @GuardedBy("mBluetoothA2dpEnabledLock") + /*package*/ void onAudioServerDiedRestoreA2dp() { + final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { + return (mA2dp != null && mAvrcpAbsVolSupported); + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { + mAvrcpAbsVolSupported = supported; + } + + /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) { + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + if (mA2dp == null) { + if (AudioService.DEBUG_VOL) { + Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp"); + return; + } + } + if (!mAvrcpAbsVolSupported) { + return; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10)); + mA2dp.setAvrcpAbsoluteVolume(index / 10); + } + } + + @GuardedBy("mA2dpAvrcpLock") + /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) { + if (mA2dp == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + if (btCodecStatus == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); + if (btCodecConfig == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); + } + + /*package*/ void receiveBtEvent(Intent intent) { + final String action = intent.getAction(); + if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { + BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + setBtScoActiveDevice(btDevice); + } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + boolean broadcast = false; + int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; + synchronized (mScoClients) { + int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + // broadcast intent if the connection was initated by AudioService + if (!mScoClients.isEmpty() + && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL + || mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATING)) { + broadcast = true; + } + switch (btState) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // startBluetoothSco called after stopBluetoothSco + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcast = false; + break; + } + } + // Tear down SCO if disconnected from external + clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; + } + } + if (broadcast) { + broadcastScoConnectionState(scoAudioState); + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); + sendStickyBroadcastToAll(newIntent); + } + } + } + + /** + * + * @return false if SCO isn't connected + */ + /*package*/ boolean isBluetoothScoOn() { + synchronized (mScoClients) { + if ((mBluetoothHeadset != null) + && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { + Log.w(TAG, "isBluetoothScoOn(true) returning false because " + + mBluetoothHeadsetDevice + " is not in audio connected mode"); + return false; + } + } + return true; + } + + /** + * Disconnect all SCO connections started by {@link AudioManager} except those started by + * {@param exceptPid} + * + * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept + */ + /*package*/ void disconnectBluetoothSco(int exceptPid) { + synchronized (mScoClients) { + checkScoAudioState(); + if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) { + return; + } + clearAllScoClients(exceptPid, true); + } + } + + /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode, + @NonNull String eventSource) { + ScoClient client = getScoClient(cb, true); + // The calling identity must be cleared before calling ScoClient.incCount(). + // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + // The caller identity must be cleared after getScoClient() because it is needed if a new + // client is created. + final long ident = Binder.clearCallingIdentity(); + try { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.incCount(scoAudioMode); + } catch (NullPointerException e) { + Log.e(TAG, "Null ScoClient", e); + } + Binder.restoreCallingIdentity(ident); + } + + /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) { + ScoClient client = getScoClient(cb, false); + // The calling identity must be cleared before calling ScoClient.decCount(). + // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + final long ident = Binder.clearCallingIdentity(); + if (client != null) { + eventSource += " client count before=" + client.getCount(); + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); + client.decCount(); + } + Binder.restoreCallingIdentity(ident); + } + + + /*package*/ void setHearingAidVolume(int index, int streamType) { + synchronized (mDeviceBroker.mHearingAidLock) { + if (mHearingAid == null) { + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: null mHearingAid"); + } + return; + } + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, + AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) { + gainDB = BT_HEARING_AID_GAIN_MIN; + } + if (AudioService.DEBUG_VOL) { + Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" + + index + " gain=" + gainDB); + } + AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( + AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); + mHearingAid.setVolume(gainDB); + } + } + + //---------------------------------------------------------------------- + private void broadcastScoConnectionState(int state) { + mDeviceBroker.broadcastScoConnectionState(state); + } + + /*package*/ void onBroadcastScoConnectionState(int state) { + if (state == mScoConnectionState) { + return; + } + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, + mScoConnectionState); + sendStickyBroadcastToAll(newIntent); + mScoConnectionState = state; + } + + private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { + if (btDevice == null) { + return true; + } + String address = btDevice.getAddress(); + BluetoothClass btClass = btDevice.getBluetoothClass(); + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + int[] outDeviceTypes = { + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, + AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT + }; + if (btClass != null) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET }; + break; + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; + break; + } + } + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + String btDeviceName = btDevice.getName(); + boolean result = false; + if (isActive) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceTypes[0], address, btDeviceName); + } else { + for (int outDeviceType : outDeviceTypes) { + result |= mDeviceBroker.handleDeviceConnection( + isActive, outDeviceType, address, btDeviceName); + } + } + // handleDeviceConnection() && result to make sure the method get executed + result = mDeviceBroker.handleDeviceConnection( + isActive, inDevice, address, btDeviceName) && result; + return result; + } + + private void setBtScoActiveDevice(BluetoothDevice btDevice) { + synchronized (mScoClients) { + Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); + final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; + if (Objects.equals(btDevice, previousActiveDevice)) { + return; + } + if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { + Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device " + + previousActiveDevice); + } + if (!handleBtScoActiveDeviceChange(btDevice, true)) { + Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice); + // set mBluetoothHeadsetDevice to null when failing to add new device + btDevice = null; + } + mBluetoothHeadsetDevice = btDevice; + if (mBluetoothHeadsetDevice == null) { + resetBluetoothSco(); + } + } + } + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + final BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mDeviceBroker.mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + if (btDevice == null) { + Log.e(TAG, "Invalid null device in BT profile listener"); + return; + } + final @BluetoothProfile.BtProfileState int state = + mA2dp.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSinkConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + } + break; + + case BluetoothProfile.A2DP_SINK: + deviceList = proxy.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + proxy.getConnectionState(btDevice); + mDeviceBroker.handleSetA2dpSourceConnectionState( + state, new BluetoothA2dpDeviceInfo(btDevice)); + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); + mBluetoothHeadset = (BluetoothHeadset) proxy; + setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice()); + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ + || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + status = connectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } + break; + case SCO_STATE_DEACTIVATE_REQ: + status = disconnectBluetoothScoAudioHelper( + mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } + break; + } + } + if (!status) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + } + break; + + case BluetoothProfile.HEARING_AID: + mHearingAid = (BluetoothHearingAid) proxy; + deviceList = mHearingAid.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + final @BluetoothProfile.BtProfileState int state = + mHearingAid.getConnectionState(btDevice); + mDeviceBroker.setBluetoothHearingAidDeviceConnectionState( + btDevice, state, + /*suppressNoisyIntent*/ false, + /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE, + /*eventSource*/ "mBluetoothProfileServiceListener"); + } + break; + + default: + break; + } + } + public void onServiceDisconnected(int profile) { + + switch (profile) { + case BluetoothProfile.A2DP: + mDeviceBroker.handleDisconnectA2dp(); + break; + + case BluetoothProfile.A2DP_SINK: + mDeviceBroker.handleDisconnectA2dpSink(); + break; + + case BluetoothProfile.HEADSET: + disconnectHeadset(); + break; + + case BluetoothProfile.HEARING_AID: + mDeviceBroker.handleDisconnectHearingAid(); + break; + + default: + break; + } + } + }; + + void disconnectAllBluetoothProfiles() { + mDeviceBroker.handleDisconnectA2dp(); + mDeviceBroker.handleDisconnectA2dpSink(); + disconnectHeadset(); + mDeviceBroker.handleDisconnectHearingAid(); + } + + private void disconnectHeadset() { + synchronized (mScoClients) { + setBtScoActiveDevice(null); + mBluetoothHeadset = null; + } + } + + //---------------------------------------------------------------------- + private class ScoClient implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private int mCreatorPid; + private int mStartcount; // number of SCO connections started by this client + + ScoClient(IBinder cb) { + mCb = cb; + mCreatorPid = Binder.getCallingPid(); + mStartcount = 0; + } + + public void binderDied() { + synchronized (mScoClients) { + Log.w(TAG, "SCO client died"); + int index = mScoClients.indexOf(this); + if (index < 0) { + Log.w(TAG, "unregistered SCO client died"); + } else { + clearCount(true); + mScoClients.remove(this); + } + } + } + + public void incCount(int scoAudioMode) { + synchronized (mScoClients) { + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + if (mStartcount == 0) { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "ScoClient incCount() could not link to " + + mCb + " binder death"); + } + } + mStartcount++; + } + } + + public void decCount() { + synchronized (mScoClients) { + if (mStartcount == 0) { + Log.w(TAG, "ScoClient.decCount() already 0"); + } else { + mStartcount--; + if (mStartcount == 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "decCount() going to 0 but not registered to binder"); + } + } + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public void clearCount(boolean stopSco) { + synchronized (mScoClients) { + if (mStartcount != 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "clearCount() mStartcount: " + + mStartcount + " != 0 but not registered to binder"); + } + } + mStartcount = 0; + if (stopSco) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public int getCount() { + return mStartcount; + } + + public IBinder getBinder() { + return mCb; + } + + public int getPid() { + return mCreatorPid; + } + + public int totalCount() { + synchronized (mScoClients) { + int count = 0; + for (ScoClient mScoClient : mScoClients) { + count += mScoClient.getCount(); + } + return count; + } + } + + private void requestScoState(int state, int scoAudioMode) { + checkScoAudioState(); + int clientCount = totalCount(); + if (clientCount != 0) { + Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode + + ", clientCount=" + clientCount); + return; + } + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + // Accept SCO audio activation only in NORMAL audio mode or if the mode is + // currently controlled by the same client process. + synchronized (mDeviceBroker.mSetModeLock) { + int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty() + ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid(); + if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { + Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " + + modeOwnerPid + " != creatorPid " + mCreatorPid); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return; + } + switch (mScoAudioState) { + case SCO_STATE_INACTIVE: + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = Settings.Global.getInt( + mDeviceBroker.getContentResolver(), + "bluetooth_sco_channel_" + + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + } + } + } + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " connection, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + Log.w(TAG, "requestScoState: no active device while connecting," + + " mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } else { + Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice + + " failed, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_DEACTIVATING: + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + break; + case SCO_STATE_DEACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to connect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + + } + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVE_INTERNAL: + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " disconnection, mScoAudioMode=" + mScoAudioMode); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + } + if (mBluetoothHeadsetDevice == null) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to disconnect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + } + } + } + + //----------------------------------------------------- + // Utilities + private void sendStickyBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.disconnectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.stopVoiceRecognition(device); + default: + return false; + } + } + + private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.connectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.startScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.startVoiceRecognition(device); + default: + return false; + } + } + + /*package*/ void resetBluetoothSco() { + synchronized (mScoClients) { + clearAllScoClients(0, false); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + AudioSystem.setParameters("A2dpSuspended=false"); + mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); + } + + + private void checkScoAudioState() { + synchronized (mScoClients) { + if (mBluetoothHeadset != null + && mBluetoothHeadsetDevice != null + && mScoAudioState == SCO_STATE_INACTIVE + && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + } + } + + + private ScoClient getScoClient(IBinder cb, boolean create) { + synchronized (mScoClients) { + for (ScoClient existingClient : mScoClients) { + if (existingClient.getBinder() == cb) { + return existingClient; + } + } + if (create) { + ScoClient newClient = new ScoClient(cb); + mScoClients.add(newClient); + return newClient; + } + return null; + } + } + + private void clearAllScoClients(int exceptPid, boolean stopSco) { + synchronized (mScoClients) { + ScoClient savedClient = null; + for (ScoClient cl : mScoClients) { + if (cl.getPid() != exceptPid) { + cl.clearCount(stopSco); + } else { + savedClient = cl; + } + } + mScoClients.clear(); + if (savedClient != null) { + mScoClients.add(savedClient); + } + } + } + + private boolean getBluetoothHeadset() { + boolean result = false; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + result = adapter.getProfileProxy(mDeviceBroker.getContext(), + mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); + } + // If we could not get a bluetooth headset proxy, send a failure message + // without delay to reset the SCO audio state and clear SCO clients. + // If we could get a proxy, send a delayed failure message that will reset our state + // in case we don't receive onServiceConnected(). + mDeviceBroker.handleFailureToConnectToBtHeadsetService( + result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0); + return result; + } + + private int mapBluetoothCodecToAudioFormat(int btCodecType) { + switch (btCodecType) { + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: + return AudioSystem.AUDIO_FORMAT_SBC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: + return AudioSystem.AUDIO_FORMAT_AAC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: + return AudioSystem.AUDIO_FORMAT_APTX; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: + return AudioSystem.AUDIO_FORMAT_APTX_HD; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: + return AudioSystem.AUDIO_FORMAT_LDAC; + default: + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + } +} diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index dc564ba69e0d..3a25d980e97a 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -202,7 +202,7 @@ public final class PlaybackActivityMonitor if (mPrivilegedAlarmActiveCount++ == 0) { mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER); - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED && @@ -211,7 +211,7 @@ public final class PlaybackActivityMonitor if (AudioSystem.getStreamVolumeIndex( AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) == mMaxAlarmVolume) { - AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM, + AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM, mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index cfc85dac8a53..326984c7202c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -34,6 +34,7 @@ import android.os.UserManagerInternal; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IntArray; import android.util.Pair; import android.util.Printer; @@ -756,6 +757,14 @@ final class InputMethodUtils { */ private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); + private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { + Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); + } + + private static final UserManagerInternal sUserManagerInternal = + LocalServices.getService(UserManagerInternal.class); + private boolean mCopyOnWrite = false; @NonNull private String mEnabledInputMethodsStrCache = ""; @@ -833,7 +842,9 @@ final class InputMethodUtils { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { - Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putStringForUser(mResolver, key, str, userId); } } @@ -852,7 +863,9 @@ final class InputMethodUtils { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { - Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) + ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; + Settings.Secure.putIntForUser(mResolver, key, value, userId); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a1646862de9f..de3f50ad8c2c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1087,7 +1087,8 @@ public class NotificationManagerService extends SystemService { || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE) || action.equals(Intent.ACTION_PACKAGES_SUSPENDED) - || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) { + || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED) + || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); String pkgList[] = null; @@ -1108,6 +1109,23 @@ public class NotificationManagerService extends SystemService { uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); cancelNotifications = false; unhideNotifications = true; + } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) { + final int distractionRestrictions = + intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS, + PackageManager.RESTRICTION_NONE); + if ((distractionRestrictions + & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + hideNotifications = true; + } else { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + cancelNotifications = false; + unhideNotifications = true; + } + } else if (queryRestart) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)}; @@ -1651,6 +1669,7 @@ public class NotificationManagerService extends SystemService { IntentFilter suspendedPkgFilter = new IntentFilter(); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); + suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, suspendedPkgFilter, null, null); @@ -7743,6 +7762,20 @@ public class NotificationManagerService extends SystemService { mPackageIntentReceiver.onReceive(getContext(), intent); } + @VisibleForTesting + protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { + // only use for testing: mimic receive broadcast that package is (un)distracting + // but does not actually register that info with packagemanager + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); + + final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + /** * Wrapper for a StatusBarNotification object that allows transfer across a oneway * binder without sending large amounts of data over a oneway transaction. diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 02fc51f89e62..ac965faedd34 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -690,6 +690,8 @@ public final class NotificationRecord { importance)); } } + // We have now gotten all the information out of the adjustments and can forget them. + mAdjustments.clear(); } } diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 3d88f20f0710..2aaa1edcfad9 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -176,6 +176,14 @@ public class NotificationShellCmd extends ShellCommand { // only use for testing mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired()); } + case "distract_package": { + // only use for testing + // Flag values are in + // {@link android.content.pm.PackageManager.DistractionRestriction}. + mDirectService.simulatePackageDistractionBroadcast( + Integer.parseInt(getNextArgRequired()), + getNextArgRequired().split(",")); + } break; case "post": case "notify": diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 692c032b1f70..6f1eeeb7de7a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -39,6 +39,7 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; @@ -258,6 +259,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHarmfulAppWarning(); case "get-harmful-app-warning": return runGetHarmfulAppWarning(); + case "get-stagedsessions": + return getStagedSessions(); case "uninstall-system-updates": return uninstallSystemUpdates(); default: { @@ -282,6 +285,28 @@ class PackageManagerShellCommand extends ShellCommand { return -1; } + private int getStagedSessions() { + final PrintWriter pw = getOutPrintWriter(); + try { + List<SessionInfo> stagedSessionsList = + mInterface.getPackageInstaller().getStagedSessions().getList(); + for (SessionInfo session: stagedSessionsList) { + pw.println("appPackageName = " + session.getAppPackageName() + + "; sessionId = " + session.getSessionId() + + "; isStaged = " + session.isStaged() + + "; isSessionReady = " + session.isSessionReady() + + "; isSessionApplied = " + session.isSessionApplied() + + "; isSessionFailed = " + session.isSessionFailed() + ";"); + } + } catch (RemoteException e) { + pw.println("Failure [" + + e.getClass().getName() + " - " + + e.getMessage() + "]"); + return 0; + } + return 1; + } + private int uninstallSystemUpdates() { final PrintWriter pw = getOutPrintWriter(); List<String> failedUninstalls = new LinkedList<>(); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index b930d267282f..c4d27e5882c4 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -153,6 +153,19 @@ public class StagingManager { return success; } + private static boolean sendMarkStagedSessionReadyRequest(int sessionId) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } + private static boolean isApexSession(@NonNull PackageInstallerSession session) { return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; } @@ -202,11 +215,18 @@ public class StagingManager { + 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 @@ public class StagingManager { "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/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java new file mode 100644 index 000000000000..a2c8dace9510 --- /dev/null +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.attention.AttentionManagerInternal; +import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.io.PrintWriter; + +/** + * Class responsible for checking if the user is currently paying attention to the phone and + * notifying {@link PowerManagerService} that user activity should be renewed. + * + * This class also implements a limit of how long the extension should be, to avoid security + * issues where the device would never be locked. + */ +public class AttentionDetector { + + private static final String TAG = "AttentionDetector"; + private static final boolean DEBUG = false; + + /** + * Invoked whenever user attention is detected. + */ + private final Runnable mOnUserAttention; + + /** + * The maximum time, in millis, that the phone can stay unlocked because of attention events, + * triggered by any user. + */ + @VisibleForTesting + protected long mMaximumExtensionMillis; + + private final Object mLock; + + /** + * {@link android.service.attention.AttentionService} API timeout. + */ + private long mMaxAttentionApiTimeoutMillis; + + /** + * Last known user activity. + */ + private long mLastUserActivityTime; + + @VisibleForTesting + protected AttentionManagerInternal mAttentionManager; + + /** + * If we're currently waiting for an attention callback + */ + private boolean mRequested; + + /** + * Current wakefulness of the device. {@see PowerManagerInternal} + */ + private int mWakefulness; + + @VisibleForTesting + final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() { + + @Override + public void onSuccess(int requestCode, int result, long timestamp) { + Slog.v(TAG, "onSuccess: " + requestCode + ", " + result + + " - current requestCode: " + getRequestCode()); + synchronized (mLock) { + if (requestCode == getRequestCode() && mRequested) { + mRequested = false; + if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + if (DEBUG) Slog.d(TAG, "Device slept before receiving callback."); + return; + } + if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) { + mOnUserAttention.run(); + } + } + } + } + + @Override + public void onFailure(int requestCode, int error) { + Slog.i(TAG, "Failed to check attention: " + error); + synchronized (mLock) { + if (requestCode == getRequestCode()) { + mRequested = false; + } + } + } + }; + + public AttentionDetector(Runnable onUserAttention, Object lock) { + mOnUserAttention = onUserAttention; + mLock = lock; + } + + public void systemReady(Context context) { + mAttentionManager = LocalServices.getService(AttentionManagerInternal.class); + mMaximumExtensionMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionMaximumExtension); + mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_attentionApiTimeout); + } + + public long updateUserActivity(long nextScreenDimming) { + if (!isAttentionServiceSupported()) { + return nextScreenDimming; + } + + final long now = SystemClock.uptimeMillis(); + final long whenToCheck = nextScreenDimming - getAttentionTimeout(); + final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis; + if (now < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now)); + } + return nextScreenDimming; + } else if (whenToStopExtending < whenToCheck) { + if (DEBUG) { + Slog.d(TAG, "Let device sleep to avoid false results and improve security " + + (whenToCheck - whenToStopExtending)); + } + return nextScreenDimming; + } else if (mRequested) { + if (DEBUG) { + Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode()); + } + return whenToCheck; + } + + // Ideally we should attribute mRequested to the result of #checkAttention, but the + // callback might arrive before #checkAttention returns (if there are cached results.) + // This means that we must assume that the request was successful, and then cancel it + // afterwards if AttentionManager couldn't deliver it. + mRequested = true; + final boolean sent = mAttentionManager.checkAttention(getRequestCode(), + getAttentionTimeout(), mCallback); + if (!sent) { + mRequested = false; + } + + Slog.v(TAG, "Checking user attention with request code: " + getRequestCode()); + return whenToCheck; + } + + /** + * Handles user activity by cancelling any pending attention requests and keeping track of when + * the activity happened. + * + * @param eventTime Activity time, in uptime millis. + * @param event Activity type as defined in {@link PowerManager}. + * @return 0 when activity was ignored, 1 when handled, -1 when invalid. + */ + public int onUserActivity(long eventTime, int event) { + switch (event) { + case PowerManager.USER_ACTIVITY_EVENT_ATTENTION: + return 0; + case PowerManager.USER_ACTIVITY_EVENT_OTHER: + case PowerManager.USER_ACTIVITY_EVENT_BUTTON: + case PowerManager.USER_ACTIVITY_EVENT_TOUCH: + case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY: + cancelCurrentRequestIfAny(); + mLastUserActivityTime = eventTime; + return 1; + default: + if (DEBUG) { + Slog.d(TAG, "Attention not reset. Unknown activity event: " + event); + } + return -1; + } + } + + public void onWakefulnessChangeStarted(int wakefulness) { + mWakefulness = wakefulness; + if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + cancelCurrentRequestIfAny(); + } + } + + private void cancelCurrentRequestIfAny() { + if (mRequested) { + mAttentionManager.cancelAttentionCheck(getRequestCode()); + mRequested = false; + } + } + + @VisibleForTesting + int getRequestCode() { + return (int) (mLastUserActivityTime % Integer.MAX_VALUE); + } + + @VisibleForTesting + long getAttentionTimeout() { + return mMaxAttentionApiTimeoutMillis; + } + + /** + * {@see AttentionManagerInternal#isAttentionServiceSupported} + */ + @VisibleForTesting + boolean isAttentionServiceSupported() { + return mAttentionManager.isAttentionServiceSupported(); + } + + public void dump(PrintWriter pw) { + pw.print("AttentionDetector:"); + pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis); + pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis); + pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime); + pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported()); + pw.print(" mRequested=" + mRequested); + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index a02787308246..3be64802c9fc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -229,6 +229,7 @@ public final class PowerManagerService extends SystemService private final BatterySaverController mBatterySaverController; private final BatterySaverStateMachine mBatterySaverStateMachine; private final BatterySavingStats mBatterySavingStats; + private final AttentionDetector mAttentionDetector; private final BinderService mBinderService; private final LocalService mLocalService; private final NativeWrapper mNativeWrapper; @@ -736,6 +737,7 @@ public final class PowerManagerService extends SystemService mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); mConstants = new Constants(mHandler); mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock); mBatterySavingStats = new BatterySavingStats(mLock); mBatterySaverPolicy = @@ -804,6 +806,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); mPolicy = getLocalService(WindowManagerPolicy.class); mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); + mAttentionDetector.systemReady(mContext); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -1326,6 +1329,16 @@ public final class PowerManagerService extends SystemService } } + private void onUserAttention() { + synchronized (mLock) { + if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */, + Process.SYSTEM_UID)) { + updatePowerStateLocked(); + } + } + } + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime @@ -1346,6 +1359,7 @@ public final class PowerManagerService extends SystemService } mNotifier.onUserActivity(event, uid); + mAttentionDetector.onUserActivity(eventTime, event); if (mUserInactiveOverrideFromWindowManager) { mUserInactiveOverrideFromWindowManager = false; @@ -1593,6 +1607,7 @@ public final class PowerManagerService extends SystemService if (mNotifier != null) { mNotifier.onWakefulnessChangeStarted(wakefulness, reason); } + mAttentionDetector.onWakefulnessChangeStarted(wakefulness); } } @@ -2085,6 +2100,10 @@ public final class PowerManagerService extends SystemService nextTimeout = -1; } + if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) { + nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout); + } + if (nextProfileTimeout > 0) { nextTimeout = Math.min(nextTimeout, nextProfileTimeout); } @@ -3477,6 +3496,7 @@ public final class PowerManagerService extends SystemService mBatterySaverPolicy.dump(pw); mBatterySaverStateMachine.dump(pw); + mAttentionDetector.dump(pw); pw.println(); final int numProfiles = mProfilePowerState.size(); diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index acede7d4fa90..9135d1db7676 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -246,6 +246,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader; + private long mDebugElapsedClockPreviousValue = 0; + private long mDebugElapsedClockPullCount = 0; + private long mDebugFailingElapsedClockPreviousValue = 0; + private long mDebugFailingElapsedClockPullCount = 0; private BatteryStatsHelper mBatteryStatsHelper = null; private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; @@ -1726,6 +1730,56 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + private void pullDebugElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + final long elapsedMillis = SystemClock.elapsedRealtime(); + final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue; + + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeLong(mDebugElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(clockDiffMillis); + e.writeInt(1 /* always set */); + pulledData.add(e); + + if (mDebugElapsedClockPullCount % 2 == 1) { + StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e2.writeLong(mDebugElapsedClockPullCount); + e2.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e2.writeLong(elapsedMillis); + e2.writeLong(clockDiffMillis); + e2.writeInt(2 /* set on odd pulls */); + pulledData.add(e2); + } + + mDebugElapsedClockPullCount++; + mDebugElapsedClockPreviousValue = elapsedMillis; + } + + private void pullDebugFailingElapsedClock(int tagId, + long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + final long elapsedMillis = SystemClock.elapsedRealtime(); + // Fails every 10 buckets. + if (mDebugFailingElapsedClockPullCount++ % 10 == 0) { + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + throw new RuntimeException("Failing debug elapsed clock"); + } + + e.writeLong(mDebugFailingElapsedClockPullCount); + e.writeLong(elapsedMillis); + // Log it twice to be able to test multi-value aggregation from ValueMetric. + e.writeLong(elapsedMillis); + e.writeLong(mDebugFailingElapsedClockPreviousValue == 0 + ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue); + mDebugFailingElapsedClockPreviousValue = elapsedMillis; + pulledData.add(e); + } + /** * Pulls various data. */ @@ -1892,6 +1946,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullTemperature(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DEBUG_ELAPSED_CLOCK: { + pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: { + pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3a077b86b89a..38580bc41f74 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 @@ class ActivityStarter { final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags + | FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java index 29645f68b2fb..ed029cd722cf 100644 --- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; + import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT; import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR; import static com.android.server.wm.AppWindowThumbnailProto.WIDTH; @@ -82,7 +85,8 @@ class AppWindowThumbnail implements Animatable { .setName("thumbnail anim: " + appToken.toString()) .setBufferSize(mWidth, mHeight) .setFormat(PixelFormat.TRANSLUCENT) - .setMetadata(appToken.windowType, + .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType) + .setMetadata(METADATA_OWNER_UID, window != null ? window.mOwnerUid : Binder.getCallingUid()) .build(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a7dd55b8a160..476bd6e9abe9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; +import static android.view.SurfaceControl.METADATA_TASK_ID; import static com.android.server.EventLogTags.WM_TASK_REMOVED; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; @@ -643,6 +644,11 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta return getAppAnimationLayer(ANIMATION_LAYER_HOME); } + @Override + SurfaceControl.Builder makeSurface() { + return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId); + } + boolean isTaskAnimating() { final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController(); if (recentsAnim != null) { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index c2a8e7efb5a5..dea3597989be 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -35,7 +37,6 @@ import android.os.IBinder; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowContentFrameStats; @@ -103,7 +104,8 @@ class WindowSurfaceController { .setBufferSize(w, h) .setFormat(format) .setFlags(flags) - .setMetadata(windowType, ownerUid); + .setMetadata(METADATA_WINDOW_TYPE, windowType) + .setMetadata(METADATA_OWNER_UID, ownerUid); mSurfaceControl = b.build(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java deleted file mode 100644 index cddb91f65d0f..000000000000 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ /dev/null @@ -1,29 +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.dhcp; - -/** - * TODO: remove this class after migrating clients. - */ -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 static final int DHCP_SUCCESS = 1; - public static final int DHCP_FAILURE = 2; -} diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java deleted file mode 100644 index a61c2efd64da..000000000000 --- a/services/net/java/android/net/ip/IpClient.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.ip; - -import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; - -import android.content.Context; -import android.net.LinkProperties; -import android.net.Network; -import android.net.ProxyInfo; -import android.net.StaticIpConfiguration; -import android.net.apf.ApfCapabilities; -import android.os.ConditionVariable; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated. - * @hide - */ -public class IpClient { - private static final String TAG = IpClient.class.getSimpleName(); - private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000; - - public static final String DUMP_ARG = "ipclient"; - - private final ConditionVariable mIpClientCv; - private final ConditionVariable mShutdownCv; - - private volatile IIpClient mIpClient; - - /** - * @see IpClientCallbacks - */ - public static class Callback extends IpClientCallbacks {} - - /** - * IpClient callback that allows clients to block until provisioning is complete. - */ - public static class WaitForProvisioningCallback extends Callback { - private final ConditionVariable mCV = new ConditionVariable(); - private LinkProperties mCallbackLinkProperties; - - /** - * Block until either {@link #onProvisioningSuccess(LinkProperties)} or - * {@link #onProvisioningFailure(LinkProperties)} is called. - */ - public LinkProperties waitForProvisioning() { - mCV.block(); - return mCallbackLinkProperties; - } - - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - mCallbackLinkProperties = newLp; - mCV.open(); - } - - @Override - public void onProvisioningFailure(LinkProperties newLp) { - mCallbackLinkProperties = null; - mCV.open(); - } - } - - private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy { - /** - * Create a new IpClientCallbacksProxy. - */ - CallbackImpl(IpClientCallbacks cb) { - super(cb); - } - - @Override - public void onIpClientCreated(IIpClient ipClient) { - mIpClient = ipClient; - mIpClientCv.open(); - super.onIpClientCreated(ipClient); - } - - @Override - public void onQuit() { - mShutdownCv.open(); - super.onQuit(); - } - } - - /** - * Create a new IpClient. - */ - public IpClient(Context context, String iface, Callback callback) { - mIpClientCv = new ConditionVariable(false); - mShutdownCv = new ConditionVariable(false); - - IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback)); - } - - /** - * @see IpClient#IpClient(Context, String, IpClient.Callback) - */ - public IpClient(Context context, String iface, Callback callback, - INetworkManagementService nms) { - this(context, iface, callback); - } - - private interface IpClientAction { - void useIpClient(IIpClient ipClient) throws RemoteException; - } - - private void doWithIpClient(IpClientAction action) { - mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - try { - action.useIpClient(mIpClient); - } catch (RemoteException e) { - Log.e(TAG, "Error communicating with IpClient", e); - } - } - - /** - * Notify IpClient that PreDhcpAction is completed. - */ - public void completedPreDhcpAction() { - doWithIpClient(c -> c.completedPreDhcpAction()); - } - - /** - * Confirm the provisioning configuration. - */ - public void confirmConfiguration() { - doWithIpClient(c -> c.confirmConfiguration()); - } - - /** - * Notify IpClient that packet filter read is complete. - */ - public void readPacketFilterComplete(byte[] data) { - doWithIpClient(c -> c.readPacketFilterComplete(data)); - } - - /** - * Shutdown the IpClient altogether. - */ - public void shutdown() { - doWithIpClient(c -> c.shutdown()); - } - - /** - * Start the IpClient provisioning. - */ - public void startProvisioning(ProvisioningConfiguration config) { - doWithIpClient(c -> c.startProvisioning(config.toStableParcelable())); - } - - /** - * Stop the IpClient. - */ - public void stop() { - doWithIpClient(c -> c.stop()); - } - - /** - * Set the IpClient TCP buffer sizes. - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes)); - } - - /** - * Set the IpClient HTTP proxy. - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo))); - } - - /** - * Set the IpClient multicast filter. - */ - public void setMulticastFilter(boolean enabled) { - doWithIpClient(c -> c.setMulticastFilter(enabled)); - } - - /** - * Dump IpClient logs. - */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args)); - } - - /** - * Block until IpClient shutdown. - */ - public void awaitShutdown() { - mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); - } - - /** - * Create a new ProvisioningConfiguration. - */ - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - - /** - * TODO: remove after migrating clients to use the shared configuration class directly. - * @see android.net.shared.ProvisioningConfiguration - */ - public static class ProvisioningConfiguration - extends android.net.shared.ProvisioningConfiguration { - public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) { - super(other); - } - - /** - * @see android.net.shared.ProvisioningConfiguration.Builder - */ - public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder { - // Override all methods to have a return type matching this Builder - @Override - public Builder withoutIPv4() { - super.withoutIPv4(); - return this; - } - - @Override - public Builder withoutIPv6() { - super.withoutIPv6(); - return this; - } - - @Override - public Builder withoutMultinetworkPolicyTracker() { - super.withoutMultinetworkPolicyTracker(); - return this; - } - - @Override - public Builder withoutIpReachabilityMonitor() { - super.withoutIpReachabilityMonitor(); - return this; - } - - @Override - public Builder withPreDhcpAction() { - super.withPreDhcpAction(); - return this; - } - - @Override - public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - super.withPreDhcpAction(dhcpActionTimeoutMs); - return this; - } - - @Override - public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - super.withStaticConfiguration(staticConfig); - return this; - } - - @Override - public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - super.withApfCapabilities(apfCapabilities); - return this; - } - - @Override - public Builder withProvisioningTimeoutMs(int timeoutMs) { - super.withProvisioningTimeoutMs(timeoutMs); - return this; - } - - @Override - public Builder withRandomMacAddress() { - super.withRandomMacAddress(); - return this; - } - - @Override - public Builder withStableMacAddress() { - super.withStableMacAddress(); - return this; - } - - @Override - public Builder withNetwork(Network network) { - super.withNetwork(network); - return this; - } - - @Override - public Builder withDisplayName(String displayName) { - super.withDisplayName(displayName); - return this; - } - - @Override - public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); - } - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java new file mode 100644 index 000000000000..9f1cbcd7ec27 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.attention.AttentionManagerInternal; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.service.attention.AttentionService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class AttentionDetectorTest extends AndroidTestCase { + + private @Mock AttentionManagerInternal mAttentionManagerInternal; + private @Mock Runnable mOnUserAttention; + private TestableAttentionDetector mAttentionDetector; + private long mAttentionTimeout; + private long mNextDimming; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any())) + .thenReturn(true); + mAttentionDetector = new TestableAttentionDetector(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE); + mAttentionDetector.setAttentionServiceSupported(true); + mNextDimming = SystemClock.uptimeMillis() + 3000L; + } + + @Test + public void testOnUserActivity_checksAttention() { + long when = registerAttention(); + verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnUserActivity_doesntCheckIfNotSupported() { + mAttentionDetector.setAttentionServiceSupported(false); + long when = registerAttention(); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(mNextDimming).isEqualTo(when); + } + + @Test + public void onUserActivity_ignoresWhiteListedActivityTypes() { + for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) { + int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i); + if (result == -1) { + throw new AssertionError("User activity " + i + " isn't listed in" + + " AttentionDetector#onUserActivity. Please consider how this new activity" + + " type affects the attention service."); + } + } + } + + @Test + public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() { + long now = SystemClock.uptimeMillis(); + mNextDimming = now; + mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(mNextDimming + 5000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_ignoresAfterMaximumExtension() { + long now = SystemClock.uptimeMillis(); + mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH); + mAttentionDetector.updateUserActivity(now + 2000L); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + } + + @Test + public void testOnUserActivity_skipsIfAlreadyScheduled() { + registerAttention(); + reset(mAttentionManagerInternal); + long when = mAttentionDetector.updateUserActivity(mNextDimming); + verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any()); + assertThat(when).isLessThan(mNextDimming); + } + + @Test + public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() { + registerAttention(); + mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP); + verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt()); + } + + @Test + public void testCallbackOnSuccess_ignoresIfNoAttention() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + @Test + public void testCallbackOnSuccess_callsCallback() { + registerAttention(); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention).run(); + } + + @Test + public void testCallbackOnFailure_unregistersCurrentRequestCode() { + registerAttention(); + mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_FAILURE_UNKNOWN); + mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(), + AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis()); + verify(mOnUserAttention, never()).run(); + } + + private long registerAttention() { + mAttentionTimeout = 4000L; + mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH); + return mAttentionDetector.updateUserActivity(mNextDimming); + } + + private class TestableAttentionDetector extends AttentionDetector { + + private boolean mAttentionServiceSupported; + + TestableAttentionDetector() { + super(AttentionDetectorTest.this.mOnUserAttention, new Object()); + mAttentionManager = mAttentionManagerInternal; + mMaximumExtensionMillis = 10000L; + } + + void setAttentionServiceSupported(boolean supported) { + mAttentionServiceSupported = supported; + } + + @Override + public boolean isAttentionServiceSupported() { + return mAttentionServiceSupported; + } + + @Override + public long getAttentionTimeout() { + return mAttentionTimeout; + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 62229235a026..9c6ab0ab9aa9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3410,6 +3410,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); + assertEquals(1, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(1, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + } + + @Test + public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() { + // Post 2 notifications from 2 packages + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("b", 1001, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgB); + + // on broadcast, hide one of the packages + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture()); + assertEquals(2, captorHide.getValue().size()); + assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName()); + + // on broadcast, unhide the package + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"}); + ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture()); + assertEquals(2, captorUnhide.getValue().size()); + assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName()); + assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName()); + } + + @Test + public void testNoNotificationsHiddenOnDistractingPackageBroadcast() { + // post notification from this package + final NotificationRecord notif1 = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + mService.addNotification(notif1); + + // on broadcast, nothing is hidden since no notifications are of package "test_package" + mService.simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"}); + ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); + assertEquals(0, captor.getValue().size()); + } + + @Test public void testCanUseManagedServicesLowRamNoWatchNullPkg() { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mActivityManager.isLowRamDevice()).thenReturn(true); diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java index c2e4581285fd..acf994610182 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java +++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java @@ -24,9 +24,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; -// TODO: fix this. either move this class into system server or add a dependency on -// these wm classes to libiorap-java and libiorap-java-tests (somehow). import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; @@ -113,12 +112,12 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected void writeToParcelImpl(Parcel p, int flags) { super.writeToParcelImpl(p, flags); - intent.writeToParcel(p, flags); + IntentProtoParcelable.write(p, intent, flags); } IntentStarted(Parcel p) { super(p); - intent = Intent.CREATOR.createFromParcel(p); + intent = IntentProtoParcelable.create(p); } } @@ -232,8 +231,7 @@ public abstract class AppLaunchEvent implements Parcelable { } public static class ActivityLaunchCancelled extends AppLaunchEvent { - public final @Nullable - @ActivityRecordProto byte[] activityRecordSnapshot; + public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot; public ActivityLaunchCancelled(@SequenceId long sequenceId, @Nullable @ActivityRecordProto byte[] snapshot) { @@ -352,7 +350,6 @@ public abstract class AppLaunchEvent implements Parcelable { ActivityLaunchCancelled.class, }; - // TODO: move to @ActivityRecordProto byte[] once we have unit tests. public static class ActivityRecordProtoParcelable { public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, int flags) { @@ -365,4 +362,31 @@ public abstract class AppLaunchEvent implements Parcelable { return data; } } + + public static class IntentProtoParcelable { + private static final int INTENT_PROTO_CHUNK_SIZE = 1024; + + public static void write(Parcel p, @NonNull Intent intent, int flags) { + // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, + // so create a new one every time. + final ProtoOutputStream protoOutputStream = + new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE); + // Write this data out as the top-most IntentProto (i.e. it is not a sub-object). + intent.writeToProto(protoOutputStream); + final byte[] bytes = protoOutputStream.getBytes(); + + p.writeByteArray(bytes); + } + + // TODO: Should be mockable for testing? + // We cannot deserialize in the platform because we don't have a 'readFromProto' + // code. + public static @NonNull Intent create(Parcel p) { + // This will "read" the correct amount of data, but then we discard it. + byte[] data = p.createByteArray(); + + // Never called by real code in a platform, this binder API is implemented only in C++. + return new Intent("<cannot deserialize IntentProto>"); + } + } } diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index 7fcad360b8fe..9a30b35f02a2 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -24,12 +24,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Handler; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityMetricsLaunchObserver; @@ -43,10 +47,20 @@ import com.android.server.wm.ActivityTaskManagerInternal; */ public class IorapForwardingService extends SystemService { - public static final boolean DEBUG = true; // TODO: read from a getprop? public static final String TAG = "IorapForwardingService"; + /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */ + public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + /** $> adb shell 'setprop iorapd.enable true' */ + private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true); + /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ + private static boolean WTF_CRASH = SystemProperties.getBoolean( + "iorapd.forwarding_service.wtf_crash", false); private IIorap mIorapRemote; + private final Object mLock = new Object(); + /** Handle onBinderDeath by periodically trying to reconnect. */ + private final Handler mHandler = + new BinderConnectionHandler(IoThread.getHandler().getLooper()); /** * Initializes the system service. @@ -58,7 +72,7 @@ public class IorapForwardingService extends SystemService { * @param context The system server context. */ public IorapForwardingService(Context context) { - super(context); + super(context); } //<editor-fold desc="Providers"> @@ -78,12 +92,40 @@ public class IorapForwardingService extends SystemService { @VisibleForTesting protected IIorap provideIorapRemote() { + IIorap iorap; try { - return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); + iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); } catch (ServiceManager.ServiceNotFoundException e) { - // TODO: how do we handle service being missing? - throw new AssertionError(e); + handleRemoteError(e); + return null; } + + try { + iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); + } catch (RemoteException e) { + handleRemoteError(e); + return null; + } + + return iorap; + } + + @VisibleForTesting + protected DeathRecipient provideDeathRecipient() { + return new DeathRecipient() { + @Override + public void binderDied() { + Log.w(TAG, "iorapd has died"); + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + }; + } + + @VisibleForTesting + protected boolean isIorapEnabled() { + // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process + // never comes up, so all binder connections will fail indefinitely. + return IS_ENABLED; } //</editor-fold> @@ -94,15 +136,128 @@ public class IorapForwardingService extends SystemService { Log.v(TAG, "onStart"); } + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + + private class BinderConnectionHandler extends Handler { + public BinderConnectionHandler(android.os.Looper looper) { + super(looper); + } + + public static final int MESSAGE_BINDER_CONNECT = 0; + + private int mAttempts = 0; + + @Override + public void handleMessage(android.os.Message message) { + switch (message.what) { + case MESSAGE_BINDER_CONNECT: + if (!retryConnectToRemoteAndConfigure(mAttempts)) { + mAttempts++; + } else { + mAttempts = 0; + } + break; + default: + throw new AssertionError("Unknown message: " + message.toString()); + } + } + } + + /** + * Handle iorapd shutdowns and crashes, by attempting to reconnect + * until the service is reached again. + * + * <p>The first connection attempt is synchronous, + * subsequent attempts are done by posting delayed tasks to the IoThread.</p> + * + * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. + */ + private boolean retryConnectToRemoteAndConfigure(int attempts) { + final int sleepTime = 1000; // ms + + if (DEBUG) { + Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); + } + + if (connectToRemoteAndConfigure()) { + return true; + } + + // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually + // called 'adb shell stop iorapd' , which means this would loop until it comes back + // up. + // + // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid + // printing this warning. + Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); + + // Use a handler instead of Thread#sleep to avoid backing up the binder thread + // when this is called from the death recipient callback. + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), + sleepTime); + + return false; + + // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); + } + + private boolean connectToRemoteAndConfigure() { + synchronized (mLock) { + // Synchronize against any concurrent calls to this via the DeathRecipient. + return connectToRemoteAndConfigureLocked(); + } + } + + private boolean connectToRemoteAndConfigureLocked() { + if (!isIorapEnabled()) { + if (DEBUG) { + Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); + } + // When we see that iorapd is disabled (when system server comes up), + // it stays disabled permanently until the next system server reset. + + // TODO: consider listening to property changes as a callback, then we can + // be more dynamic about handling enable/disable. + return true; + } + // Connect to the native binder service. mIorapRemote = provideIorapRemote(); + if (mIorapRemote == null) { + Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); + return false; + } invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) ); + registerInProcessListenersLocked(); + + return true; + } + + private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); + private boolean mRegisteredListeners = false; + + private void registerInProcessListenersLocked() { + if (mRegisteredListeners) { + // Listeners are registered only once (idempotent operation). + // + // Today listeners are tolerant of the remote side going away + // by handling remote errors. + // + // We could try to 'unregister' the listener when we get a binder disconnect, + // but we'd still have to handle the case of encountering synchronous errors so + // it really wouldn't be a win (other than having less log spew). + return; + } // Listen to App Launch Sequence events from ActivityTaskManager, // and forward them to the native binder service. ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); - launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver()); + launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); + + mRegisteredListeners = true; } private class AppLaunchObserver implements ActivityMetricsLaunchObserver { @@ -110,6 +265,8 @@ public class IorapForwardingService extends SystemService { // launch sequences on the native side. private @AppLaunchEvent.SequenceId long mSequenceId = -1; + // All callbacks occur on the same background thread. Don't synchronize explicitly. + @Override public void onIntentStarted(@NonNull Intent intent) { // #onIntentStarted [is the only transition that] initiates a new launch sequence. @@ -174,7 +331,7 @@ public class IorapForwardingService extends SystemService { invokeRemote(() -> mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity)) + new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity)) ); } } @@ -201,6 +358,7 @@ public class IorapForwardingService extends SystemService { } } + /** Allow passing lambdas to #invokeRemote */ private interface RemoteRunnable { void run() throws RemoteException; } @@ -209,8 +367,26 @@ public class IorapForwardingService extends SystemService { try { r.run(); } catch (RemoteException e) { - // TODO: what do we do with exceptions? - throw new AssertionError("not implemented", e); + // This could be a logic error (remote side returning error), which we need to fix. + // + // This could also be a DeadObjectException in which case its probably just iorapd + // being manually restarted. + // + // Don't make any assumption, since DeadObjectException could also mean iorapd crashed + // unexpectedly. + // + // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. + handleRemoteError(e); } } + + private static void handleRemoteError(Throwable t) { + if (WTF_CRASH) { + // In development modes, we just want to crash. + throw new AssertionError("unexpected remote error", t); + } else { + // Log to wtf which gets sent to dropbox, and in system_server this does not crash. + Log.wtf(TAG, t); + } + } } diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml index f83a16ec0916..919154d3e48a 100644 --- a/startop/iorap/tests/AndroidTest.xml +++ b/startop/iorap/tests/AndroidTest.xml @@ -33,6 +33,15 @@ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"> </target_preparer> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- Crash instead of using Log.wtf within the system_server iorap code. --> + <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" /> + <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status --> + <option name="set-property" key="iorapd.binder.fake" value="true" /> + <option name="restore-properties" value="true" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.google.android.startop.iorap.tests" /> <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java new file mode 100644 index 000000000000..ac4d17a0ce65 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsException.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class defines an IMS-related exception that has been thrown while interacting with a + * device or carrier provided ImsService implementation. + * @hide + */ +@SystemApi +public class ImsException extends Exception { + + /** + * The operation has failed due to an unknown or unspecified error. + */ + public static final int CODE_ERROR_UNSPECIFIED = 0; + /** + * The operation has failed because there is no {@link ImsService} available to service it. This + * may be due to an {@link ImsService} crash or other illegal state. + * <p> + * This is a temporary error and the operation may be retried until the connection to the + * {@link ImsService} is restored. + */ + public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; + + /** + * This device or carrier configuration does not support IMS for this subscription. + * <p> + * This is a permanent configuration error and there should be no retry. + */ + public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CODE_ERROR_", value = { + CODE_ERROR_UNSPECIFIED, + CODE_ERROR_SERVICE_UNAVAILABLE, + CODE_ERROR_UNSUPPORTED_OPERATION + }) + public @interface ImsErrorCode {} + + private int mCode = CODE_ERROR_UNSPECIFIED; + + /** + * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message) { + super(getMessage(message, CODE_ERROR_UNSPECIFIED)); + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code. + * @param message an optional message to detail the error condition more specifically. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code) { + super(getMessage(message, code)); + mCode = code; + } + + /** + * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a + * {@link Throwable} that contains the original error that was thrown to lead to this Exception. + * @param message an optional message to detail the error condition more specifically. + * @param cause the {@link Throwable} that caused this {@link ImsException} to be created. + */ + public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) { + super(getMessage(message, code), cause); + mCode = code; + } + + /** + * @return the IMS Error code that is associated with this {@link ImsException}. + */ + public @ImsErrorCode int getCode() { + return mCode; + } + + private static String getMessage(String message, int code) { + StringBuilder builder; + if (!TextUtils.isEmpty(message)) { + builder = new StringBuilder(message); + builder.append(" (code: "); + builder.append(code); + builder.append(")"); + return builder.toString(); + } else { + return "code: " + code; + } + } +} diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 5b2e635b179f..eb99d5dcaaeb 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; * registration and MmTel capability status callbacks, as well as query/modify user settings for the * associated subscription. * - * @see #createForSubscriptionId(Context, int) + * @see #createForSubscriptionId(int) * @hide */ @SystemApi @@ -315,15 +315,12 @@ public class ImsMmTelManager { /** * Create an instance of ImsManager for the subscription id specified. * - * @param context The context to create this ImsMmTelManager instance within. * @param subId The ID of the subscription that this ImsMmTelManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ImsMmTelManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ImsMmTelManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -331,7 +328,7 @@ public class ImsMmTelManager { } /** - * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead. + * Only visible for testing, use {@link #createForSubscriptionId(int)} instead. * @hide */ @VisibleForTesting @@ -341,7 +338,7 @@ public class ImsMmTelManager { /** * Registers a {@link RegistrationCallback} with the system, which will provide registration - * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use + * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up. * @@ -354,13 +351,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@CallbackExecutor Executor executor, - @NonNull RegistrationCallback c) { + @NonNull RegistrationCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -372,6 +370,8 @@ public class ImsMmTelManager { getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -403,7 +403,7 @@ public class ImsMmTelManager { /** * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service * availability updates for the subscription specified in - * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)} + * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)} * can also be used to query this information at any time. * * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to @@ -419,13 +419,14 @@ public class ImsMmTelManager { * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or * {@link CapabilityCallback} callback. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull CapabilityCallback c) { + @NonNull CapabilityCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } @@ -437,6 +438,8 @@ public class ImsMmTelManager { getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -796,14 +799,6 @@ public class ImsMmTelManager { } } - private static SubscriptionManager getSubscriptionManager(Context context) { - SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); - if (manager == null) { - throw new RuntimeException("Could not find SubscriptionManager."); - } - return manager; - } - private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( ServiceManager.getService(Context.TELEPHONY_SERVICE)); diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 086a76546b2d..b171f7940944 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -172,15 +172,13 @@ public class ProvisioningManager { /** * Create a new {@link ProvisioningManager} for the subscription specified. - * @param context The context that this manager will use. + * * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() - * @throws IllegalArgumentException if the subscription is invalid or - * the subscription ID is not an active subscription. + * @throws IllegalArgumentException if the subscription is invalid. */ - public static ProvisioningManager createForSubscriptionId(Context context, int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId) - || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + public static ProvisioningManager createForSubscriptionId(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); } @@ -202,18 +200,21 @@ public class ProvisioningManager { * @see SubscriptionManager.OnSubscriptionsChangedListener * @throws IllegalArgumentException if the subscription associated with this callback is not * active (SIM is not inserted, ESIM inactive) or the subscription is invalid. - * @throws IllegalStateException if the subscription associated with this callback is valid, but + * @throws ImsException if the subscription associated with this callback is valid, but * the {@link ImsService} associated with the subscription is not available. This can happen if - * the service crashed, for example. + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor, - @NonNull Callback callback) { + @NonNull Callback callback) throws ImsException { callback.setExecutor(executor); try { getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); + } catch (IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -332,6 +333,7 @@ public class ProvisioningManager { * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @@ -358,6 +360,7 @@ public class ProvisioningManager { * provisioning, false if the capability does require provisioning and has not been * provisioned yet. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index f35e88672a23..fea763ed5785 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -21,8 +21,10 @@ import android.telephony.ims.ImsReasonInfo; /** * This class defines a general IMS-related exception. * + * @deprecated Use {@link android.telephony.ims.ImsException} instead. * @hide */ +@Deprecated public class ImsException extends Exception { /** diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index 07c4a938cf9f..c16efbda1830 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { R.id.benchmark_text_low_hitrate, R.id.benchmark_edit_text_input, R.id.benchmark_overdraw, + R.id.benchmark_bitmap_upload, }; public static class LocalBenchmarksList extends ListFragment { @@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { case R.id.benchmark_text_low_hitrate: case R.id.benchmark_edit_text_input: case R.id.benchmark_overdraw: + case R.id.benchmark_bitmap_upload: case R.id.benchmark_memory_bandwidth: case R.id.benchmark_memory_latency: case R.id.benchmark_power_management: @@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { intent = new Intent(getApplicationContext(), EditTextInputActivity.class); break; case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class); + break; + case R.id.benchmark_bitmap_upload: intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); break; case R.id.benchmark_memory_bandwidth: diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java index 89c6aedd8b5c..5723c599d91a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -229,6 +229,8 @@ public class BenchmarkRegistry { return context.getString(R.string.cpu_gflops_name); case R.id.benchmark_overdraw: return context.getString(R.string.overdraw_name); + case R.id.benchmark_bitmap_upload: + return context.getString(R.string.bitmap_upload_name); default: return "Some Benchmark"; } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java index 787090208d7e..7692836cfacc 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; import com.android.benchmark.ui.automation.Automator; import com.android.benchmark.ui.automation.Interaction; @@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity { final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); - mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload); + + mAutomator = new Automator(name, runId, iteration, getWindow(), new Automator.AutomateCallback() { @Override public void onPostAutomate() { diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml index 6801fd9f61ec..694e0d9a6917 100644 --- a/tests/JankBench/app/src/main/res/values/ids.xml +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -23,6 +23,7 @@ <item name="benchmark_text_low_hitrate" type="id" /> <item name="benchmark_edit_text_input" type="id" /> <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_bitmap_upload" type="id" /> <item name="benchmark_memory_bandwidth" type="id" /> <item name="benchmark_memory_latency" type="id" /> <item name="benchmark_power_management" type="id" /> diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml index 270adf89e4ed..5c2405899db9 100644 --- a/tests/JankBench/app/src/main/res/values/strings.xml +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ <string name="edit_text_input_description">Tests edit text input</string> <string name="overdraw_name">Overdraw Test</string> <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="bitmap_upload_name">Bitmap Upload Test</string> + <string name="bitmap_upload_description">Tests bitmap upload</string> <string name="memory_bandwidth_name">Memory Bandwidth</string> <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> <string name="memory_latency_name">Memory Latency</string> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml index 07c453c25359..fccc7b9d3776 100644 --- a/tests/JankBench/app/src/main/res/xml/benchmark.xml +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -62,6 +62,12 @@ benchmark:category="ui" benchmark:description="@string/overdraw_description" /> + <com.android.benchmark.Benchmark + benchmark:name="@string/bitmap_upload_name" + benchmark:id="@id/benchmark_bitmap_upload" + benchmark:category="ui" + benchmark:description="@string/bitmap_upload_description" /> + <!-- <com.android.benchmark.Benchmark benchmark:name="@string/memory_bandwidth_name" diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 7783e108f674..8f752871355f 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -182,7 +182,8 @@ cc_test_host { defaults: ["aapt2_defaults"], data: [ "integration-tests/CompileTest/**/*", - "integration-tests/CommandTests/**/*" + "integration-tests/CommandTests/**/*", + "integration-tests/ConvertTest/**/*" ], } diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 85f90806752f..7a74ba925ba0 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -284,6 +284,8 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer // The table might be modified by below code. auto converted_table = apk->GetResourceTable(); + std::unordered_set<std::string> files_written; + // Resources for (const auto& package : converted_table->packages) { for (const auto& type : package->types) { @@ -297,10 +299,14 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer return 1; } - if (!serializer->SerializeFile(file, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) - << "failed to serialize file " << *file->path); - return 1; + // Only serialize if we haven't seen this file before + if (files_written.insert(*file->path).second) { + if (!serializer->SerializeFile(file, output_writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize file " + << *file->path); + return 1; + } } } // file } // config_value diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp index 8da5bb8d5dd6..3c0fe370c516 100644 --- a/tools/aapt2/cmd/Convert_test.cpp +++ b/tools/aapt2/cmd/Convert_test.cpp @@ -18,6 +18,7 @@ #include "LoadedApk.h" #include "test/Test.h" +#include "ziparchive/zip_archive.h" using testing::Eq; using testing::Ne; @@ -103,4 +104,45 @@ TEST_F(ConvertTest, KeepRawXmlStrings) { EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); } +TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) { + StdErrDiagnostics diag; + const std::string apk_path = + file::BuildPath({android::base::GetExecutableDirectory(), + "integration-tests", "ConvertTest", "duplicate_entries.apk"}); + + const std::string out_convert_apk = GetTestPath("out_convert.apk"); + std::vector<android::StringPiece> convert_args = { + "-o", out_convert_apk, + "--output-format", "proto", + apk_path + }; + ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0)); + + ZipArchiveHandle handle; + ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0)); + + void* cookie = nullptr; + + ZipString prefix("res/theme/10"); + int32_t result = StartIteration(handle, &cookie, &prefix, nullptr); + + // If this is -5, that means we've found a duplicate entry and this test has failed + EXPECT_THAT(result, Eq(0)); + + // But if read succeeds, verify only one res/theme/10 entry + int count = 0; + + // Can't pass nullptrs into Next() + ZipString zip_name; + ZipEntry zip_data; + + while ((result = Next(cookie, &zip_data, &zip_name)) == 0) { + count++; + } + + EndIteration(cookie); + + EXPECT_THAT(count, Eq(1)); +} + } // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk Binary files differnew file mode 100644 index 000000000000..c558a334b369 --- /dev/null +++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 35fba3dcf7cf..488de8789178 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.net.NetworkInfo.DetailedState; @@ -120,8 +121,14 @@ public class WifiInfo implements Parcelable { @UnsupportedAppUsage private String mMacAddress = DEFAULT_MAC_ADDRESS; + /** + * Whether the network is ephemeral or not. + */ private boolean mEphemeral; + /** + * Whether the network is trusted or not. + */ private boolean mTrusted; /** @@ -130,6 +137,12 @@ public class WifiInfo implements Parcelable { private boolean mOsuAp; /** + * If connected to a network suggestion or specifier, store the package name of the app, + * else null. + */ + private String mNetworkSuggestionOrSpecifierPackageName; + + /** * Running total count of lost (not ACKed) transmitted unicast data packets. * @hide */ @@ -209,6 +222,7 @@ public class WifiInfo implements Parcelable { setMeteredHint(false); setEphemeral(false); setOsuAp(false); + setNetworkSuggestionOrSpecifierPackageName(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -240,6 +254,8 @@ public class WifiInfo implements Parcelable { mMeteredHint = source.mMeteredHint; mEphemeral = source.mEphemeral; mTrusted = source.mTrusted; + mNetworkSuggestionOrSpecifierPackageName = + source.mNetworkSuggestionOrSpecifierPackageName; mOsuAp = source.mOsuAp; txBad = source.txBad; txRetries = source.txRetries; @@ -476,6 +492,17 @@ public class WifiInfo implements Parcelable { return mOsuAp; } + /** {@hide} */ + public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) { + mNetworkSuggestionOrSpecifierPackageName = packageName; + } + + /** {@hide} */ + public @Nullable String getNetworkSuggestionOrSpecifierPackageName() { + return mNetworkSuggestionOrSpecifierPackageName; + } + + /** @hide */ @UnsupportedAppUsage public void setNetworkId(int id) { @@ -634,6 +661,7 @@ public class WifiInfo implements Parcelable { dest.writeDouble(rxSuccessRate); mSupplicantState.writeToParcel(dest, flags); dest.writeInt(mOsuAp ? 1 : 0); + dest.writeString(mNetworkSuggestionOrSpecifierPackageName); } /** Implement the Parcelable interface {@hide} */ @@ -672,6 +700,7 @@ public class WifiInfo implements Parcelable { info.rxSuccessRate = in.readDouble(); info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); info.mOsuAp = in.readInt() != 0; + info.mNetworkSuggestionOrSpecifierPackageName = in.readString(); return info; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8086039799e5..066823931832 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1404,7 +1404,6 @@ public class WifiManager { * {@link #reject()} to return the user's selection back to the platform via this callback. * @hide */ - @SystemApi public interface NetworkRequestUserSelectionCallback { /** * User selected this network to connect to. @@ -1428,7 +1427,6 @@ public class WifiManager { * or reject the request by the app. * @hide */ - @SystemApi public interface NetworkRequestMatchCallback { /** * Invoked to register a callback to be invoked to convey user selection. The callback @@ -1605,7 +1603,6 @@ public class WifiManager { * object. If null, then the application's main thread will be used. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback, @Nullable Handler handler) { @@ -1635,7 +1632,6 @@ public class WifiManager { * @param callback Callback for network match events * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback( @NonNull NetworkRequestMatchCallback callback) { diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index 677bf371c781..948dcfa47f59 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -35,6 +35,7 @@ public class WifiInfoTest { private static final long TEST_TX_RETRIES = 2; private static final long TEST_TX_BAD = 3; private static final long TEST_RX_SUCCESS = 4; + private static final String TEST_PACKAGE_NAME = "com.test.example"; /** * Verify parcel write/read with WifiInfo. @@ -48,6 +49,7 @@ public class WifiInfoTest { writeWifiInfo.rxSuccess = TEST_RX_SUCCESS; writeWifiInfo.setTrusted(true); writeWifiInfo.setOsuAp(true); + writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME); Parcel parcel = Parcel.obtain(); writeWifiInfo.writeToParcel(parcel, 0); @@ -62,5 +64,6 @@ public class WifiInfoTest { assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess); assertTrue(readWifiInfo.isTrusted()); assertTrue(readWifiInfo.isOsuAp()); + assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName()); } } |