Merge "Check if Test Harness Mode is enabling on boot"
diff --git a/Android.bp b/Android.bp
index a5cc89c..d4a04cc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -726,7 +726,7 @@
"ext",
],
- jarjar_rules: ":framework-hidl-jarjar",
+ jarjar_rules: "jarjar_rules_hidl.txt",
static_libs: [
"apex_aidl_interface-java",
@@ -738,6 +738,15 @@
"android.hardware.cas-V1.0-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.health-V1.0-java-constants",
+ "android.hardware.radio-V1.0-java",
+ "android.hardware.radio-V1.1-java",
+ "android.hardware.radio-V1.2-java",
+ "android.hardware.radio-V1.3-java",
+ "android.hardware.radio-V1.4-java",
+ "android.hardware.radio.config-V1.0-java",
+ "android.hardware.radio.config-V1.1-java",
+ "android.hardware.radio.config-V1.2-java",
+ "android.hardware.radio.deprecated-V1.0-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
@@ -746,15 +755,12 @@
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb-V1.2-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.wifi-V1.0-java-constants",
- "android.hardware.radio-V1.0-java",
- "android.hardware.radio-V1.3-java",
- "android.hardware.radio-V1.4-java",
- "android.hardware.usb.gadget-V1.0-java",
"networkstack-aidl-interfaces-java",
"netd_aidl_interface-java",
"devicepolicyprotosnano",
@@ -788,11 +794,6 @@
],
}
-filegroup {
- name: "framework-hidl-jarjar",
- srcs: ["jarjar_rules_hidl.txt"],
-}
-
java_library {
name: "framework",
defaults: ["framework-defaults"],
diff --git a/api/current.txt b/api/current.txt
index bb6dbeb..124ad58 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5101,7 +5101,7 @@
}
public class KeyguardManager {
- method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
+ method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
method @Deprecated public boolean inKeyguardRestrictedInputMode();
method public boolean isDeviceLocked();
@@ -13947,8 +13947,6 @@
@AnyThread public abstract class ColorSpace {
method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]);
method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation);
- method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int);
- method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace);
@@ -16498,6 +16496,7 @@
ctor public BiometricPrompt.Builder(android.content.Context);
method public android.hardware.biometrics.BiometricPrompt build();
method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
+ method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean);
method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
@@ -44662,6 +44661,7 @@
public final class SmsManager {
method public String createAppSpecificSmsToken(android.app.PendingIntent);
+ method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
method public java.util.ArrayList<java.lang.String> divideMessage(String);
method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
method public android.os.Bundle getCarrierConfigValues();
@@ -48568,6 +48568,7 @@
method public String getName();
method @Deprecated public int getOrientation();
method @Deprecated public int getPixelFormat();
+ method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
method public long getPresentationDeadlineNanos();
method public void getRealMetrics(android.util.DisplayMetrics);
method public void getRealSize(android.graphics.Point);
diff --git a/api/system-current.txt b/api/system-current.txt
index 9e3e800..e5ae9a7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -508,11 +508,13 @@
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
+ method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+ field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
@@ -1303,13 +1305,13 @@
field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
- field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
+ field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
@@ -1695,18 +1697,17 @@
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
method public int getRollbackId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
- field public final android.content.rollback.PackageRollbackInfo targetPackage;
}
public final class RollbackManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
}
@@ -5042,6 +5043,7 @@
package android.os {
public class BatteryManager {
+ method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
}
@@ -5533,7 +5535,6 @@
method @Deprecated public final void attachBaseContext(android.content.Context);
method @Deprecated public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
- method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService";
}
@@ -8244,6 +8245,16 @@
field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants;
}
+ public class ImsException extends java.lang.Exception {
+ ctor public ImsException(@Nullable String);
+ ctor public ImsException(@Nullable String, int);
+ ctor public ImsException(@Nullable String, int, Throwable);
+ method public int getCode();
+ field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
+ field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
+ field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
+ }
+
public final class ImsExternalCallState implements android.os.Parcelable {
ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean);
method public int describeContents();
@@ -8261,7 +8272,7 @@
}
public class ImsMmTelManager {
- method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int);
+ method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
@@ -8270,8 +8281,8 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -8706,13 +8717,13 @@
}
public class ProvisioningManager {
- method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
+ method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
diff --git a/api/test-current.txt b/api/test-current.txt
index 0f2ba12..ae29e49 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,6 +6,7 @@
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+ field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
@@ -1053,6 +1054,10 @@
method public static java.io.File getStorageDirectory();
}
+ public class FileUtils {
+ method public static boolean contains(java.io.File, java.io.File);
+ }
+
public abstract class HwBinder implements android.os.IHwBinder {
ctor public HwBinder();
method public static final void configureRpcThreadpool(long, boolean);
@@ -1218,6 +1223,10 @@
method public boolean hasSingleFileDescriptor();
}
+ public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+ method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
+ }
+
public final class PowerManager {
method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode();
method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int);
@@ -1485,17 +1494,37 @@
package android.provider {
+ public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns {
+ field public static final String[] SYNC_WRITABLE_COLUMNS;
+ }
+
+ public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns {
+ field public static final String[] SYNC_WRITABLE_COLUMNS;
+ }
+
+ public final class ContactsContract {
+ field public static final String HIDDEN_COLUMN_PREFIX = "x_";
+ }
+
public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
}
+ public static final class ContactsContract.PinnedPositions {
+ field public static final String UNDEMOTE_METHOD = "undemote";
+ }
+
public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
field public static final android.net.Uri CORP_CONTENT_URI;
}
public final class MediaStore {
- method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+ method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
+ method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ field public static final String SCAN_FILE_CALL = "scan_file";
+ field public static final String SCAN_VOLUME_CALL = "scan_volume";
}
public final class Settings {
@@ -1555,6 +1584,10 @@
field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
}
+ public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
+ field public static final String _DATA = "_data";
+ }
+
}
package android.security {
@@ -1829,10 +1862,15 @@
}
public class TelephonyManager {
+ method public int checkCarrierPrivilegesForPackage(String);
method public int getCarrierIdListVersion();
method public boolean isRttSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method public void setCarrierTestOverride(String, String, String, String, String, String, String);
+ field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
+ field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
+ field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
+ field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9c320d3..b26c713 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -989,6 +989,25 @@
return Status::ok();
}
+Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+ const String16& packageName,
+ vector<int64_t>* output) {
+ ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+ IPCThreadState* ipc = IPCThreadState::self();
+ mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender);
+ //TODO: Return the list of configs that are already active
+ return Status::ok();
+}
+
+Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) {
+ ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+ IPCThreadState* ipc = IPCThreadState::self();
+ mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid());
+ return Status::ok();
+}
+
Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
ENFORCE_DUMP_AND_USAGE_STATS(packageName);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index fe0504f..cdff50f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -132,6 +132,17 @@
const String16& packageName) override;
/**
+ * Binder call to let clients register the active configs changed operation.
+ */
+ virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+ const String16& packageName,
+ vector<int64_t>* output) override;
+
+ /**
+ * Binder call to remove the active configs changed operation for the specified package..
+ */
+ virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override;
+ /**
* Binder call to allow clients to remove the specified configuration.
*/
virtual Status removeConfiguration(int64_t key,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d6b4737f..ac99817 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -163,7 +163,7 @@
// Consider removing this if it becomes a problem
ServiceStateChanged service_state_changed = 99;
ServiceLaunchReported service_launch_reported = 100;
- PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+ FlagFlipUpdateOccurred flag_flip_update_occurred = 101;
BinaryPushStateChanged binary_push_state_changed = 102;
DevicePolicyEvent device_policy_event = 103;
DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104;
@@ -209,10 +209,11 @@
AdbConnectionChanged adb_connection_changed = 144;
SpeechDspStatReported speech_dsp_stat_reported = 145;
UsbContaminantReported usb_contaminant_reported = 146;
+ WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
}
// 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 +261,8 @@
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.
@@ -1483,6 +1486,25 @@
optional android.bluetooth.hci.StatusEnum reason_code = 9;
}
+/**
+ * Logs when a module is rolled back by Watchdog.
+ *
+ * Logged from: Rollback Manager
+ */
+message WatchdogRollbackOccurred {
+ enum RollbackType {
+ UNKNOWN = 0;
+ ROLLBACK_INITIATE = 1;
+ ROLLBACK_SUCCESS = 2;
+ ROLLBACK_FAILURE = 3;
+ }
+ optional RollbackType rollback_type = 1;
+
+ optional string package_name = 2;
+
+ optional int32 package_version_code = 3;
+}
+
/**
* Logs when something is plugged into or removed from the USB-C connector.
@@ -2476,18 +2498,14 @@
}
/*
- * Logs when a flag flip state changes.
- * Logged in P/h.
+ * Logs when a flag flip update occurrs. Used for mainline modules that update via flag flips.
*/
-message PhenotypeFlagStateChanged {
- // Mendel configuration name.
- optional string mendel_config_name = 1;
- // State
- enum State {
- STATE_UNKNOWN = 0;
- COMMITTED = 1;
- }
- optional State state = 2;
+message FlagFlipUpdateOccurred {
+ // If the event is from a flag config package, specify the package name.
+ optional string flag_flip_package_name = 1;
+
+ // The order id of the package
+ optional int64 order_id = 2;
}
/*
@@ -4557,3 +4575,39 @@
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/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 5fea90b..aa22333 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -128,6 +128,17 @@
mConfigReceivers.erase(key);
}
+void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
+ const sp<IBinder>& intentSender) {
+ lock_guard<mutex> lock(mMutex);
+ mActiveConfigsChangedReceivers[uid] = intentSender;
+}
+
+void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
+ lock_guard<mutex> lock(mMutex);
+ mActiveConfigsChangedReceivers.erase(uid);
+}
+
void ConfigManager::RemoveConfig(const ConfigKey& key) {
vector<sp<ConfigListener>> broadcastList;
{
@@ -181,6 +192,11 @@
mConfigReceivers.erase(*it);
}
+ auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
+ if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
+ mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
+ }
+
mConfigs.erase(uidIt);
for (const sp<ConfigListener>& listener : mListeners) {
@@ -213,6 +229,7 @@
}
mConfigReceivers.clear();
+ mActiveConfigsChangedReceivers.clear();
for (const sp<ConfigListener>& listener : mListeners) {
broadcastList.push_back(listener);
}
@@ -250,6 +267,17 @@
}
}
+const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+ lock_guard<mutex> lock(mMutex);
+
+ auto it = mActiveConfigsChangedReceivers.find(uid);
+ if (it == mActiveConfigsChangedReceivers.end()) {
+ return nullptr;
+ } else {
+ return it->second;
+ }
+}
+
void ConfigManager::Dump(FILE* out) {
lock_guard<mutex> lock(mMutex);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 122e669..c064a51 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -82,6 +82,23 @@
void RemoveConfigReceiver(const ConfigKey& key);
/**
+ * Sets the broadcast receiver that is notified whenever the list of active configs
+ * changes for this uid.
+ */
+ void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+
+ /**
+ * Returns the broadcast receiver for active configs changed for this uid.
+ */
+
+ const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+
+ /**
+ * Erase any active configs changed broadcast receiver associated with this uid.
+ */
+ void RemoveActiveConfigsChangedReceiver(const int uid);
+
+ /**
* A configuration was removed.
*
* Reports this to listeners.
@@ -130,6 +147,12 @@
std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
/**
+ * Each uid can be subscribed by up to one receiver to notify that the list of active configs
+ * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+ */
+ std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+
+ /**
* The ConfigListeners that will be told about changes.
*/
std::vector<sp<ConfigListener>> mListeners;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 7e56bee..4585a09 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -209,6 +209,14 @@
{android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
{.puller = new StatsCompanionServicePuller(
android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+ // DebugElapsedClock.
+ {android::util::DEBUG_ELAPSED_CLOCK,
+ {.additiveFields = {1, 2, 3, 4},
+ .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+ // DebugFailingElapsedClock.
+ {android::util::DEBUG_FAILING_ELAPSED_CLOCK,
+ {.additiveFields = {1, 2, 3, 4},
+ .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
// BuildInformation.
{android::util::BUILD_INFORMATION,
{.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index ee2fe8f..8e7a58b 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -901,7 +901,6 @@
Landroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel;
Landroid/os/ParcelFileDescriptor;-><init>(Ljava/io/FileDescriptor;)V
Landroid/os/ParcelFileDescriptor;->fromData([BLjava/lang/String;)Landroid/os/ParcelFileDescriptor;
-Landroid/os/ParcelFileDescriptor;->getFile(Ljava/io/FileDescriptor;)Ljava/io/File;
Landroid/os/ParcelFileDescriptor;->seekTo(J)J
Landroid/os/PerformanceCollector;-><init>()V
Landroid/os/PerformanceCollector;->beginSnapshot(Ljava/lang/String;)V
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 181acce..f522d71 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -29,6 +29,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.hardware.biometrics.BiometricPrompt;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -87,6 +88,12 @@
"android.app.action.CONFIRM_FRP_CREDENTIAL";
/**
+ * @hide
+ */
+ public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE =
+ "android.app.extra.BIOMETRIC_PROMPT_BUNDLE";
+
+ /**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
* @hide
@@ -101,12 +108,6 @@
public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
/**
- * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
- * @hide
- */
- public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
-
- /**
* A CharSequence description to show to the user on the alternate button when used with
* {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
* @hide
@@ -124,44 +125,23 @@
public static final int RESULT_ALTERNATE = 1;
/**
- * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
- * for the current user of the device. The caller is expected to launch this activity using
- * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)}
+ *
+ * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
+ * if enrolled) for the current user of the device. The caller is expected to launch this
+ * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
*
- * @param title Title to be shown on the dialog.
- * @param description Description to be shown on the dialog.
* @return the intent for launching the activity or null if no password is required.
**/
+ @Deprecated
@RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
- public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
- return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
- }
-
- /**
- * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
- * for the current user of the device. The caller is expected to launch this activity using
- * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
- * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
- *
- * @param title Title to be shown on the dialog.
- * @param description Description to be shown on the dialog.
- * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
- * {@link android.hardware.biometrics.BiometricPrompt} with
- * {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
- * boolean)} set to false.
- * @return the intent for launching the activity or null if no password is required.
- * @hide
- */
- public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
- boolean useImplicit) {
- if (!isDeviceSecure()) {
- return null;
- }
+ public Intent createConfirmDeviceCredentialIntent(CharSequence title,
+ CharSequence description) {
+ if (!isDeviceSecure()) return null;
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
- intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
// explicitly set the package for security
intent.setPackage(getSettingsPackageForIntent(intent));
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 9dcd122..9b66c92 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -73,6 +73,11 @@
*/
public static final String EXTRA_STATS_DIMENSIONS_VALUE =
"android.app.extra.STATS_DIMENSIONS_VALUE";
+ /**
+ * Long array extra of the active configs for the uid that added those configs.
+ */
+ public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS =
+ "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
/**
* Broadcast Action: Statsd has started.
@@ -274,6 +279,43 @@
}
}
+ /**
+ * Registers the operation that is called whenever there is a change in which configs are
+ * active. This must be called each time statsd starts. This operation allows
+ * statsd to inform clients that they should pull data of the configs that are currently
+ * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
+ * that are active and stop pulling data of configs that are no longer active.
+ *
+ * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+ * associated with the given subscriberId. May be null, in which case
+ * it removes any associated pending intent for this client.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
+ public void setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
+ throws StatsUnavailableException {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (pendingIntent == null) {
+ service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
+ } else {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ service.setActiveConfigsChangedOperation(intentSender,
+ mContext.getOpPackageName());
+ }
+
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Failed to connect to statsd when registering active configs listener.");
+ throw new StatsUnavailableException("could not connect", e);
+ } catch (SecurityException e) {
+ throw new StatsUnavailableException(e.getMessage(), e);
+ }
+ }
+ }
+
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 171c2f5..c4bf1eb 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -434,7 +434,7 @@
* {@inheritDoc}
*/
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public @BtProfileState int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
try {
mServiceLock.readLock().lock();
@@ -689,7 +689,7 @@
* @hide
*/
@UnsupportedAppUsage
- public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+ public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
try {
mServiceLock.readLock().lock();
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 78560d2..2cb7b2d 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -16,6 +16,7 @@
package android.bluetooth;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,7 +43,7 @@
public static final String EXTRA_CODEC_STATUS =
"android.bluetooth.codec.extra.CODEC_STATUS";
- private final BluetoothCodecConfig mCodecConfig;
+ private final @Nullable BluetoothCodecConfig mCodecConfig;
private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
@@ -140,7 +141,7 @@
* @return the current codec configuration
*/
@UnsupportedAppUsage
- public BluetoothCodecConfig getCodecConfig() {
+ public @Nullable BluetoothCodecConfig getCodecConfig() {
return mCodecConfig;
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index b8670db..ef77596 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -18,11 +18,14 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -60,6 +63,16 @@
/** The profile is in disconnecting state */
int STATE_DISCONNECTING = 3;
+ /** @hide */
+ @IntDef({
+ STATE_DISCONNECTED,
+ STATE_CONNECTING,
+ STATE_CONNECTED,
+ STATE_DISCONNECTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtProfileState {}
+
/**
* Headset and Handsfree profile
*/
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 6704a45..47a4a2d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -87,6 +87,7 @@
* <p>For more information about using a ContentResolver with content providers, read the
* <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
* developer guide.</p>
+ * </div>
*/
public abstract class ContentResolver implements ContentInterface {
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e7aaf6..22f73db 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2375,8 +2375,7 @@
public static final String ACTION_PACKAGE_ENABLE_ROLLBACK =
"android.intent.action.PACKAGE_ENABLE_ROLLBACK";
/**
- * Broadcast Action: An existing version of an application package has been
- * rolled back to a previous version.
+ * Broadcast Action: A rollback has been committed.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
@@ -2385,8 +2384,8 @@
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED =
- "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
+ public static final String ACTION_ROLLBACK_COMMITTED =
+ "android.intent.action.ROLLBACK_COMMITTED";
/**
* @hide
* Broadcast Action: Ask system services if there is any reason to
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 2faf3ad..4f4c34b 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -27,5 +27,4 @@
*/
oneway interface IRuntimePermissionPresenter {
void getAppPermissions(String packageName, in RemoteCallback callback);
- void revokeRuntimePermission(String packageName, String permissionName);
}
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 420bcb6..63d75a0 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -17,17 +17,13 @@
package android.content.rollback;
import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
import android.content.rollback.RollbackInfo;
import android.content.IntentSender;
/** {@hide} */
interface IRollbackManager {
- RollbackInfo getAvailableRollback(String packageName);
-
- StringParceledListSlice getPackagesWithAvailableRollbacks();
-
+ ParceledListSlice getAvailableRollbacks();
ParceledListSlice getRecentlyExecutedRollbacks();
void executeRollback(in RollbackInfo rollback, String callerPackageName,
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0803a7c..8532b5a 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -20,6 +20,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.List;
+
/**
* Information about a set of packages that can be, or already have been
* rolled back together.
@@ -34,25 +36,20 @@
*/
private final int mRollbackId;
- /**
- * The package that needs to be rolled back.
- */
- public final PackageRollbackInfo targetPackage;
+ private final List<PackageRollbackInfo> mPackages;
- // TODO: Add a list of additional packages rolled back due to atomic
- // install dependencies when rollback of atomic installs is supported.
// TODO: Add a flag to indicate if reboot is required, when rollback of
// staged installs is supported.
/** @hide */
- public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+ public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
this.mRollbackId = rollbackId;
- this.targetPackage = targetPackage;
+ this.mPackages = packages;
}
private RollbackInfo(Parcel in) {
mRollbackId = in.readInt();
- targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
}
/**
@@ -62,6 +59,13 @@
return mRollbackId;
}
+ /**
+ * Returns the list of package that are rolled back.
+ */
+ public List<PackageRollbackInfo> getPackages() {
+ return mPackages;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -70,7 +74,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mRollbackId);
- targetPackage.writeToParcel(out, flags);
+ out.writeTypedList(mPackages);
}
public static final Parcelable.Creator<RollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index c1c0bc1..2566ac5 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -17,7 +17,6 @@
package android.content.rollback;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -50,55 +49,26 @@
}
/**
- * Returns the rollback currently available to be executed for the given
- * package.
- * <p>
- * The returned RollbackInfo describes what packages would be rolled back,
- * including package version codes before and after rollback. The rollback
- * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}.
- * <p>
- * TODO: What if there is no package installed on device for packageName?
+ * Returns a list of all currently available rollbacks.
*
- * @param packageName name of the package to get the availble RollbackInfo for.
- * @return the rollback available for the package, or null if no rollback
- * is available for the package.
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) {
+ public List<RollbackInfo> getAvailableRollbacks() {
try {
- return mBinder.getAvailableRollback(packageName);
+ return mBinder.getAvailableRollbacks().getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Gets the names of packages that are available for rollback.
- * Call {@link #getAvailableRollback(String)} to get more information
- * about the rollback available for a particular package.
- *
- * @return the names of packages that are available for rollback.
- * @throws SecurityException if the caller does not have the
- * MANAGE_ROLLBACKS permission.
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @NonNull List<String> getPackagesWithAvailableRollbacks() {
- try {
- return mBinder.getPackagesWithAvailableRollbacks().getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
-
- /**
- * Gets the list of all recently executed rollbacks.
+ * Gets the list of all recently committed rollbacks.
* This is for the purposes of preventing re-install of a bad version of a
- * package.
+ * package and monitoring the status of a staged rollback.
* <p>
- * Returns an empty list if there are no recently executed rollbacks.
+ * Returns an empty list if there are no recently committed rollbacks.
* <p>
* To avoid having to keep around complete rollback history forever on a
* device, the returned list of rollbacks is only guaranteed to include
@@ -107,12 +77,12 @@
* (without the possibility of rollback) to a higher version code than was
* rolled back from.
*
- * @return the recently executed rollbacks
+ * @return the recently committed rollbacks
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() {
+ public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
try {
return mBinder.getRecentlyExecutedRollbacks().getList();
} catch (RemoteException e) {
@@ -121,25 +91,24 @@
}
/**
- * Execute the given rollback, rolling back all versions of the packages
- * to the last good versions previously installed on the device as
- * specified in the given rollback object. The rollback will fail if any
- * of the installed packages or available rollbacks are inconsistent with
- * the versions specified in the given rollback object, which can happen
- * if a package has been updated or a rollback expired since the rollback
- * object was retrieved from {@link #getAvailableRollback(String)}.
+ * Commit the rollback with given id, rolling back all versions of the
+ * packages to the last good versions previously installed on the device
+ * as specified in the corresponding RollbackInfo object. The
+ * rollback will fail if any of the installed packages or available
+ * rollbacks are inconsistent with the versions specified in the given
+ * rollback object, which can happen if a package has been updated or a
+ * rollback expired since the rollback object was retrieved from
+ * {@link #getAvailableRollbacks()}.
* <p>
* TODO: Specify the returns status codes.
- * TODO: What happens in case reboot is required for the rollback to take
- * effect for staged installs?
*
- * @param rollback to execute
+ * @param rollback to commit
* @param statusReceiver where to deliver the results
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public void executeRollback(@NonNull RollbackInfo rollback,
+ public void commitRollback(@NonNull RollbackInfo rollback,
@NonNull IntentSender statusReceiver) {
try {
mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d99..209afb8 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,55 @@
public static final int FACE_ACQUIRED_TOO_SIMILAR = 15;
/**
+ * The magnitude of the pan angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The pan angle is defined as the angle swept out by the user’s face turning
+ * their neck left and right. The pan angle would be zero if the user faced the
+ * camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
+
+ /**
+ * The magnitude of the tilt angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The tilt angle is defined as the angle swept out by the user’s face looking up
+ * and down. The pan angle would be zero if the user faced the camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
+
+ /**
+ * The magnitude of the roll angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The roll angle is defined as the angle swept out by the user’s face tilting their head
+ * towards their shoulders to the left and right. The pan angle would be zero if the user
+ * faced the camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
+
+ /**
+ * The user’s face has been obscured by some object.
+ *
+ * The user should be informed to remove any objects from the line of sight from
+ * the sensor to the user’s face.
+ */
+ public static final int FACE_ACQUIRED_FACE_OBSCURED = 19;
+
+ /**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
* the above categories. Vendors are responsible for providing error strings for these errors.
*
* @hide
*/
- public static final int FACE_ACQUIRED_VENDOR = 16;
+ public static final int FACE_ACQUIRED_VENDOR = 20;
/**
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 8aac1bf..5afe1a9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -171,5 +171,39 @@
Slog.w(TAG, "resetTimeout(): Service not connected");
}
}
+
+ /**
+ * TODO(b/123378871): Remove when moved.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onConfirmDeviceCredentialSuccess() {
+ if (mService != null) {
+ try {
+ mService.onConfirmDeviceCredentialSuccess();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected");
+ }
+ }
+
+ /**
+ * TODO(b/123378871): Remove when moved.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onConfirmDeviceCredentialError(int error, String message) {
+ if (mService != null) {
+ try {
+ mService.onConfirmDeviceCredentialError(error, message);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c69b68e4..ec62aba 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -77,6 +77,10 @@
* @hide
*/
public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
+ /**
+ * @hide
+ */
+ public static final String KEY_ENABLE_FALLBACK = "enable_fallback";
/**
* Error/help message will show for this amount of time.
@@ -242,6 +246,18 @@
}
/**
+ * The user will first be prompted to authenticate with biometrics, but also given the
+ * option to authenticate with their device PIN, pattern, or password.
+ * @param enable When true, the prompt will fall back to ask for the user's device
+ * credentials (PIN, pattern, or password).
+ * @return
+ */
+ public Builder setEnableFallback(boolean enable) {
+ mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
* @return a {@link BiometricPrompt}
* @throws IllegalArgumentException if any of the required fields are not set.
@@ -250,11 +266,15 @@
final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
+ final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK);
if (TextUtils.isEmpty(title) && !useDefaultTitle) {
throw new IllegalArgumentException("Title must be set and non-empty");
- } else if (TextUtils.isEmpty(negative)) {
+ } else if (TextUtils.isEmpty(negative) && !enableFallback) {
throw new IllegalArgumentException("Negative text must be set and non-empty");
+ } else if (!TextUtils.isEmpty(negative) && enableFallback) {
+ throw new IllegalArgumentException("Can't have both negative button behavior"
+ + " and fallback enabled");
}
return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
}
@@ -514,6 +534,9 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
+ if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) {
+ throw new IllegalArgumentException("Fallback not supported with crypto");
+ }
authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index de828f2..e4336d1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -51,4 +51,12 @@
// Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetTimeout(in byte [] token);
+
+ // TODO(b/123378871): Remove when moved.
+ // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's
+ // setEnableFallback method, since there's no way for us to intercept onActivityResult.
+ // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult.
+ void onConfirmDeviceCredentialSuccess();
+ // TODO(b/123378871): Remove when moved.
+ void onConfirmDeviceCredentialError(int error, String message);
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7e45441..f3ebd7f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
+import android.graphics.ColorSpace;
import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
import android.media.projection.IMediaProjection;
@@ -80,12 +81,20 @@
new ArrayList<DisplayListenerDelegate>();
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+ private final ColorSpace mWideColorSpace;
private int[] mDisplayIdCache;
private int mWifiDisplayScanNestCount;
private DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
+ try {
+ mWideColorSpace =
+ ColorSpace.get(
+ ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
}
/**
@@ -495,6 +504,17 @@
}
/**
+ * Gets the preferred wide gamut color space for all displays.
+ * The wide gamut color space is returned from composition pipeline
+ * based on hardware capability.
+ *
+ * @hide
+ */
+ public ColorSpace getPreferredWideGamutColorSpace() {
+ return mWideColorSpace;
+ }
+
+ /**
* Sets the global brightness configuration for a given user.
*
* @hide
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aae8afb..5ea8bd6 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -116,4 +116,9 @@
// Get the minimum brightness curve.
Curve getMinimumBrightnessCurve();
+
+ // Gets the id of the preferred wide gamut color space for all displays.
+ // The wide gamut color space is returned from composition pipeline
+ // based on hardware capability.
+ int getPreferredWideGamutColorSpaceId();
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 954071a..3fc5e41 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,6 +16,8 @@
package android.os;
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
@@ -369,4 +371,26 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the delay for reporting battery state as charging after device is plugged in.
+ * This allows machine-learning or heuristics to delay the reporting and the corresponding
+ * broadcast, based on battery level, charging rate, and/or other parameters.
+ *
+ * @param delayMillis the delay in milliseconds, negative value to reset.
+ *
+ * @return True if the delay was set successfully.
+ *
+ * @see ACTION_CHARGING
+ * @hide
+ */
+ @RequiresPermission(permission.POWER_SAVER)
+ @SystemApi
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ try {
+ return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index eae1aa5..1919fcc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -834,10 +834,10 @@
* also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility"
+ "other", "button", "touch", "accessibility", "attention"
};
- public static final int NUM_USER_ACTIVITY_TYPES = 4;
+ public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index d463b44..6f5f807 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 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 @@
* @hide
*/
// TODO: Expose API when the implementation is more complete.
-// @SystemApi
+//@SystemApi
@SystemService(Context.BUGREPORT_SERVICE)
public class BugreportManager {
private final Context mContext;
@@ -47,47 +49,46 @@
}
/**
- * An interface describing the listener for bugreport progress and status.
+ * An interface describing the callback for bugreport progress and status.
*/
- public interface BugreportListener {
+ public abstract static class BugreportCallback {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+ BUGREPORT_ERROR_INVALID_INPUT,
+ BUGREPORT_ERROR_RUNTIME,
+ BUGREPORT_ERROR_USER_DENIED_CONSENT,
+ BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT
+ })
+
+ /** Possible error codes taking a bugreport can encounter */
+ public @interface BugreportErrorCode {}
+
+ /** The input options were invalid */
+ public static final int BUGREPORT_ERROR_INVALID_INPUT =
+ IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+
+ /** A runtime error occured */
+ public static final int BUGREPORT_ERROR_RUNTIME =
+ IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
+
+ /** User denied consent to share the bugreport */
+ public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
+ IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
+
+ /** The request to get user consent timed out. */
+ public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+ IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
/**
* Called when there is a progress update.
* @param progress the progress in [0.0, 100.0]
*/
- void onProgress(float progress);
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
- BUGREPORT_ERROR_INVALID_INPUT,
- BUGREPORT_ERROR_RUNTIME
- })
-
- /** Possible error codes taking a bugreport can encounter */
- @interface BugreportErrorCode {}
-
- /** The input options were invalid */
- int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
-
- /** A runtime error occured */
- int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
-
- /** User denied consent to share the bugreport */
- int BUGREPORT_ERROR_USER_DENIED_CONSENT =
- IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
-
- /** The request to get user consent timed out. */
- int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
- IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+ public void onProgress(float progress) {}
/**
* Called when taking bugreport resulted in an error.
*
- * @param errorCode the error that occurred. Possible values are
- * {@code BUGREPORT_ERROR_INVALID_INPUT},
- * {@code BUGREPORT_ERROR_RUNTIME},
- * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT},
- * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}.
- *
* <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
* consent to sharing the bugreport with the calling app.
*
@@ -95,19 +96,19 @@
* out, but the bugreport could be available in the internal directory of dumpstate for
* manual retrieval.
*/
- void onError(@BugreportErrorCode int errorCode);
+ public void onError(@BugreportErrorCode int errorCode) {}
/**
* Called when taking bugreport finishes successfully.
*/
- void onFinished();
+ public void onFinished() {}
}
/**
* Starts a bugreport.
*
* <p>This starts a bugreport in the background. However the call itself can take several
- * seconds to return in the worst case. {@code listener} will receive progress and status
+ * seconds to return in the worst case. {@code callback} will receive progress and status
* updates.
*
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the
@@ -118,19 +119,23 @@
* @param screenshotFd file to write the screenshot, if necessary. This should be opened
* in write-only, append mode.
* @param params options that specify what kind of a bugreport should be taken
- * @param listener callback for progress and status updates
+ * @param callback callback for progress and status updates
*/
@RequiresPermission(android.Manifest.permission.DUMP)
- public void startBugreport(@NonNull FileDescriptor bugreportFd,
- @Nullable FileDescriptor screenshotFd,
- @NonNull BugreportParams params, @NonNull BugreportListener listener) {
+ public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
+ @Nullable ParcelFileDescriptor screenshotFd,
+ @NonNull BugreportParams params,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback) {
// TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
- DumpstateListener dsListener = new DumpstateListener(listener);
-
+ DumpstateListener dsListener = new DumpstateListener(executor, callback);
try {
// Note: mBinder can get callingUid from the binder transaction.
mBinder.startBugreport(-1 /* callingUid */,
- mContext.getOpPackageName(), bugreportFd, screenshotFd,
+ mContext.getOpPackageName(),
+ (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()),
+ (screenshotFd != null
+ ? screenshotFd.getFileDescriptor() : new FileDescriptor()),
params.getMode(), dsListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -151,10 +156,12 @@
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
- private final BugreportListener mListener;
+ private final Executor mExecutor;
+ private final BugreportCallback mCallback;
- DumpstateListener(@Nullable BugreportListener listener) {
- mListener = listener;
+ DumpstateListener(Executor executor, @Nullable BugreportCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
}
@Override
@@ -164,19 +171,37 @@
@Override
public void onProgress(int progress) throws RemoteException {
- mListener.onProgress(progress);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onProgress(progress);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(int errorCode) throws RemoteException {
- mListener.onError(errorCode);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onError(errorCode);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onFinished() throws RemoteException {
+ final long identity = Binder.clearCallingIdentity();
try {
- mListener.onFinished();
+ mExecutor.execute(() -> {
+ mCallback.onFinished();
+ });
} finally {
+ Binder.restoreCallingIdentity(identity);
// The bugreport has finished. Let's shutdown the service to minimize its footprint.
cancelBugreport();
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51c3c4c..629289b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -39,6 +39,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.provider.DocumentsContract.Document;
import android.system.ErrnoException;
@@ -852,6 +853,7 @@
*
* @hide
*/
+ @TestApi
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
return contains(dir.getAbsolutePath(), file.getAbsolutePath());
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 74d434c..93d6f4c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -119,6 +119,23 @@
void removeDataFetchOperation(long configKey, in String packageName);
/**
+ * Registers the given pending intent for this packagename. This intent is invoked when the
+ * active status of any of the configs sent by this package changes and will contain a list of
+ * config ids that are currently active. It also returns the list of configs that are currently
+ * active. There can be at most one active configs changed listener per package.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ long[] setActiveConfigsChangedOperation(in IBinder intentSender, in String packageName);
+
+ /**
+ * Removes the active configs changed operation for the specified package name.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void removeActiveConfigsChangedOperation(in String packageName);
+
+ /**
* Removes the configuration with the matching config key. No-op if this config key does not
* exist.
*
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 630bd2e..d68eeed 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,7 @@
import static android.system.OsConstants.S_ISREG;
import static android.system.OsConstants.S_IWOTH;
+import android.annotation.TestApi;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.os.MessageQueue.OnFileDescriptorEventListener;
@@ -580,6 +581,7 @@
*
* @hide
*/
+ @TestApi
public static File getFile(FileDescriptor fd) throws IOException {
try {
final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4ce760f..7f4254e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -336,6 +336,13 @@
public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
/**
+ * User activity event type: {@link android.service.attention.AttentionService} taking action
+ * on behalf of user.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_ATTENTION = 4;
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
index 8d568c8..33795f8 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -78,15 +78,6 @@
public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions(
@NonNull String packageName);
- /**
- * Revokes the permission {@code permissionName} for app {@code packageName}
- *
- * @param packageName The package for which to revoke
- * @param permissionName The permission to revoke
- */
- public abstract void onRevokeRuntimePermission(@NonNull String packageName,
- @NonNull String permissionName);
-
@Override
public final IBinder onBind(Intent intent) {
return new IRuntimePermissionPresenter.Stub() {
@@ -99,17 +90,6 @@
obtainMessage(RuntimePermissionPresenterService::getAppPermissions,
RuntimePermissionPresenterService.this, packageName, callback));
}
-
- @Override
- public void revokeRuntimePermission(String packageName, String permissionName) {
- checkNotNull(packageName, "packageName");
- checkNotNull(permissionName, "permissionName");
-
- mHandler.sendMessage(
- obtainMessage(RuntimePermissionPresenterService::onRevokeRuntimePermission,
- RuntimePermissionPresenterService.this, packageName,
- permissionName));
- }
};
}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 8bd75d7..8a52f1f 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.AlarmManager;
@@ -805,6 +806,7 @@
*
* @hide
*/
+ @TestApi
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
ACCOUNT_NAME,
ACCOUNT_TYPE,
@@ -1832,6 +1834,7 @@
*
* @hide
*/
+ @TestApi
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
_SYNC_ID,
DIRTY,
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 25554b9..81e1eb9 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -126,6 +126,7 @@
* Prefix for column names that are not visible to client apps.
* @hide
*/
+ @TestApi
public static final String HIDDEN_COLUMN_PREFIX = "x_";
/**
@@ -8444,6 +8445,7 @@
* nothing will be done.
* @hide
*/
+ @TestApi
public static final String UNDEMOTE_METHOD = "undemote";
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5c442f..887175a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -104,6 +104,11 @@
*/
public static final String VOLUME_EXTERNAL = "external";
+ /** {@hide} */ @TestApi
+ public static final String SCAN_FILE_CALL = "scan_file";
+ /** {@hide} */ @TestApi
+ public static final String SCAN_VOLUME_CALL = "scan_volume";
+
/**
* The method name used by the media scanner and mtp to tell the media provider to
* rescan and reclassify that have become unhidden because of renaming folders or
@@ -2992,6 +2997,7 @@
*
* @hide
*/
+ @TestApi
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
@@ -3022,6 +3028,7 @@
*
* @hide
*/
+ @TestApi
public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5222fc3..852b65a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14280,6 +14280,17 @@
*/
public static final String APPOP_HISTORY_PARAMETERS =
"appop_history_parameters";
+
+ /**
+ * Delay for sending ACTION_CHARGING after device is plugged in.
+ * This is used as an override for constants defined in BatteryStatsImpl for
+ * ease of experimentation.
+ *
+ * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS
+ * @hide
+ */
+ public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY =
+ "battery_charging_state_update_delay";
}
/**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 140336e..dce5d56 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -18,6 +18,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -289,6 +290,7 @@
* Path to the media content file. Internal only field.
* @hide
*/
+ @TestApi
public static final String _DATA = "_data";
// Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f58efc9..e3a6bd7 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.ColorSpace;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -953,6 +954,24 @@
}
/**
+ * Returns the preferred wide color space of the Display.
+ * The returned wide gamut color space is based on hardware capability and
+ * is preferred by the composition pipeline.
+ * Returns null if the display doesn't support wide color gamut.
+ * {@link Display#isWideColorGamut()}.
+ */
+ @Nullable
+ public ColorSpace getPreferredWideGamutColorSpace() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ if (mDisplayInfo.isWideColorGamut()) {
+ return mGlobal.getPreferredWideGamutColorSpace();
+ }
+ return null;
+ }
+ }
+
+ /**
* Gets the supported color modes of this device.
* @hide
*/
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 863b717..4032a6b8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -46,10 +46,9 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -60,6 +59,7 @@
import libcore.util.NativeAllocationRegistry;
import java.io.Closeable;
+import java.nio.ByteBuffer;
/**
* Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
@@ -75,7 +75,7 @@
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
- int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid)
+ int w, int h, int format, int flags, long parentObject, Parcel metadata)
throws OutOfResourcesException;
private static native long nativeReadFromParcel(Parcel in);
private static native long nativeCopyFromSurfaceControl(long nativeObject);
@@ -182,6 +182,7 @@
private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
IBinder toToken);
private static native boolean nativeGetProtectedContentSupport();
+ private static native void nativeSetMetadata(long transactionObj, int key, Parcel data);
private final CloseGuard mCloseGuard = CloseGuard.get();
private String mName;
@@ -413,6 +414,24 @@
}
/**
+ * owner UID.
+ * @hide
+ */
+ public static final int METADATA_OWNER_UID = 1;
+
+ /**
+ * Window type as per {@link WindowManager.LayoutParams}.
+ * @hide
+ */
+ public static final int METADATA_WINDOW_TYPE = 2;
+
+ /**
+ * Task id to allow association between surfaces and task.
+ * @hide
+ */
+ public static final int METADATA_TASK_ID = 3;
+
+ /**
* Builder class for {@link SurfaceControl} objects.
*/
public static class Builder {
@@ -423,8 +442,7 @@
private int mFormat = PixelFormat.OPAQUE;
private String mName;
private SurfaceControl mParent;
- private int mWindowType = -1;
- private int mOwnerUid = -1;
+ private SparseIntArray mMetadata;
/**
* Begin building a SurfaceControl with a given {@link SurfaceSession}.
@@ -455,8 +473,8 @@
throw new IllegalArgumentException(
"Only buffer layers can set a valid buffer size.");
}
- return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
- mFlags, mParent, mWindowType, mOwnerUid);
+ return new SurfaceControl(
+ mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata);
}
/**
@@ -581,23 +599,17 @@
}
/**
- * Set surface metadata.
+ * Sets a metadata int.
*
- * Currently these are window-types as per {@link WindowManager.LayoutParams} and
- * owner UIDs. Child surfaces inherit their parents
- * metadata so only the WindowManager needs to set this on root Surfaces.
- *
- * @param windowType A window-type
- * @param ownerUid UID of the window owner.
+ * @param key metadata key
+ * @param data associated data
* @hide
*/
- public Builder setMetadata(int windowType, int ownerUid) {
- if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
- throw new UnsupportedOperationException(
- "It only makes sense to set Surface metadata from the WindowManager");
+ public Builder setMetadata(int key, int data) {
+ if (mMetadata == null) {
+ mMetadata = new SparseIntArray();
}
- mWindowType = windowType;
- mOwnerUid = ownerUid;
+ mMetadata.put(key, data);
return this;
}
@@ -682,13 +694,12 @@
* @param h The surface initial height.
* @param flags The surface creation flags. Should always include {@link #HIDDEN}
* in the creation flags.
- * @param windowType The type of the window as specified in WindowManager.java.
- * @param ownerUid A unique per-app ID.
+ * @param metadata Initial metadata.
*
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
- SurfaceControl parent, int windowType, int ownerUid)
+ SurfaceControl parent, SparseIntArray metadata)
throws OutOfResourcesException, IllegalArgumentException {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
@@ -706,8 +717,21 @@
mName = name;
mWidth = w;
mHeight = h;
- mNativeObject = nativeCreate(session, name, w, h, format, flags,
- parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
+ Parcel metaParcel = Parcel.obtain();
+ try {
+ if (metadata != null && metadata.size() > 0) {
+ metaParcel.writeInt(metadata.size());
+ for (int i = 0; i < metadata.size(); ++i) {
+ metaParcel.writeInt(metadata.keyAt(i));
+ metaParcel.writeByteArray(
+ ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array());
+ }
+ }
+ mNativeObject = nativeCreate(session, name, w, h, format, flags,
+ parent != null ? parent.mNativeObject : 0, metaParcel);
+ } finally {
+ metaParcel.recycle();
+ }
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
@@ -2326,6 +2350,30 @@
}
/**
+ * Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
+ * @hide
+ */
+ public Transaction setMetadata(int key, int data) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(data);
+ try {
+ setMetadata(key, parcel);
+ } finally {
+ parcel.recycle();
+ }
+ return this;
+ }
+
+ /**
+ * Sets an arbitrary piece of metadata on the surface.
+ * @hide
+ */
+ public Transaction setMetadata(int key, Parcel data) {
+ nativeSetMetadata(mNativeObject, key, data);
+ return this;
+ }
+
+ /**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
*
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 42acb09..803462d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -98,6 +102,20 @@
private static final boolean DEBUG = false;
+
+ /**
+ * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+ * {@link AppPredictionManager} will be queried for direct share targets.
+ */
+ // TODO(b/123089490): Replace with system flag
+ private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+ // TODO(b/123088566) Share these in a better way.
+ private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+ private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+ public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ private AppPredictor mAppPredictor;
+ private AppPredictor.Callback mAppPredictorCallback;
+
/**
* If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
* binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@
mChooserShownTime = System.currentTimeMillis();
final long systemCost = mChooserShownTime - intentReceivedTime;
MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
+
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ final IntentFilter filter = getTargetIntentFilter();
+ Bundle extras = new Bundle();
+ extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+ AppPredictionManager appPredictionManager =
+ getSystemService(AppPredictionManager.class);
+ mAppPredictor = appPredictionManager.createAppPredictionSession(
+ new AppPredictionContext.Builder(this)
+ .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+ .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+ .setExtras(extras)
+ .build());
+ mAppPredictorCallback = resultList -> {
+ final List<DisplayResolveInfo> driList =
+ getDisplayResolveInfos(mChooserListAdapter);
+ final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
+ new ArrayList<>();
+ for (AppTarget appTarget : resultList) {
+ shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
+ appTarget.getShortcutInfo(),
+ new ComponentName(
+ appTarget.getPackageName(), appTarget.getClassName())));
+ }
+ sendShareShortcutInfoList(shareShortcutInfos, driList);
+ };
+ mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
+ }
+
if (DEBUG) {
Log.d(TAG, "System Time Cost is " + systemCost);
}
@@ -339,6 +386,10 @@
}
unbindRemainingServices();
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+ mAppPredictor.destroy();
+ }
}
@Override
@@ -606,15 +657,10 @@
}
}
- private void queryDirectShareTargets(ChooserListAdapter adapter) {
- final IntentFilter filter = getTargetIntentFilter();
- if (filter == null) {
- return;
- }
-
+ private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
// Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
// and use the old code path. This Ugliness should go away when Sharesheet is refactored.
- final List<DisplayResolveInfo> driList = new ArrayList<>();
+ List<DisplayResolveInfo> driList = new ArrayList<>();
int targetsToQuery = 0;
for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -634,42 +680,59 @@
break;
}
}
+ return driList;
+ }
+
+ private void queryDirectShareTargets(ChooserListAdapter adapter) {
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ mAppPredictor.requestPredictionUpdate();
+ return;
+ }
+ final IntentFilter filter = getTargetIntentFilter();
+ if (filter == null) {
+ return;
+ }
+ final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
AsyncTask.execute(() -> {
ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
-
- // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
- // for direct share targets. After ShareSheet is refactored we should use the
- // ShareShortcutInfos directly.
- boolean resultMessageSent = false;
- for (int i = 0; i < driList.size(); i++) {
- List<ChooserTarget> chooserTargets = new ArrayList<>();
- for (int j = 0; j < resultList.size(); j++) {
- if (driList.get(i).getResolvedComponentName().equals(
- resultList.get(j).getTargetComponent())) {
- chooserTargets.add(convertToChooserTarget(resultList.get(j)));
- }
- }
- if (chooserTargets.isEmpty()) {
- continue;
- }
-
- final Message msg = Message.obtain();
- msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
- msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
- mChooserHandler.sendMessage(msg);
- resultMessageSent = true;
- }
-
- if (resultMessageSent) {
- final Message msg = Message.obtain();
- msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
- mChooserHandler.sendMessage(msg);
- }
+ sendShareShortcutInfoList(resultList, driList);
});
}
+ private void sendShareShortcutInfoList(
+ List<ShortcutManager.ShareShortcutInfo> resultList,
+ List<DisplayResolveInfo> driList) {
+ // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
+ // for direct share targets. After ShareSheet is refactored we should use the
+ // ShareShortcutInfos directly.
+ boolean resultMessageSent = false;
+ for (int i = 0; i < driList.size(); i++) {
+ List<ChooserTarget> chooserTargets = new ArrayList<>();
+ for (int j = 0; j < resultList.size(); j++) {
+ if (driList.get(i).getResolvedComponentName().equals(
+ resultList.get(j).getTargetComponent())) {
+ chooserTargets.add(convertToChooserTarget(resultList.get(j)));
+ }
+ }
+ if (chooserTargets.isEmpty()) {
+ continue;
+ }
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
+ msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+ mChooserHandler.sendMessage(msg);
+ resultMessageSent = true;
+ }
+
+ if (resultMessageSent) {
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+ mChooserHandler.sendMessage(msg);
+ }
+ }
+
private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
Bundle extras = new Bundle();
@@ -724,6 +787,7 @@
// Do nothing. We'll send the voice stuff ourselves.
}
+ // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
void updateModelAndChooserCounts(TargetInfo info) {
if (info != null) {
final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 514ff76..d7514d1 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -154,4 +154,7 @@
oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info);
oneway void noteModemControllerActivity(in ModemActivityInfo info);
oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info);
+
+ /** {@hide} */
+ boolean setChargingStateUpdateDelayMillis(int delay);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 534361e..c6afee2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13395,11 +13395,22 @@
mResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
false /* notifyForDescendants */, this);
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY),
+ false /* notifyForDescendants */, this);
updateConstants();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
+ if (uri.equals(
+ Settings.Global.getUriFor(
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) {
+ synchronized (BatteryStatsImpl.this) {
+ updateBatteryChargedDelayMsLocked();
+ }
+ return;
+ }
updateConstants();
}
@@ -13443,12 +13454,21 @@
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
- BATTERY_CHARGED_DELAY_MS = mParser.getInt(
- KEY_BATTERY_CHARGED_DELAY_MS,
- DEFAULT_BATTERY_CHARGED_DELAY_MS);
+ updateBatteryChargedDelayMsLocked();
}
}
+ private void updateBatteryChargedDelayMsLocked() {
+ // a negative value indicates that we should ignore this override
+ final int delay = Settings.Global.getInt(mResolver,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+ -1);
+
+ BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
+ KEY_BATTERY_CHARGED_DELAY_MS,
+ DEFAULT_BATTERY_CHARGED_DELAY_MS);
+ }
+
private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
if (isEnabled && !wasEnabled) {
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index dd7633a..acb34ba 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_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 69877c7..f1b259e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -124,7 +124,7 @@
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
- jint windowType, jint ownerUid) {
+ jobject metadataParcel) {
ScopedUtfChars name(env, nameStr);
sp<SurfaceComposerClient> client;
if (sessionObj != NULL) {
@@ -134,8 +134,18 @@
}
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface;
+ LayerMetadata metadata;
+ Parcel* parcel = parcelForJavaObject(env, metadataParcel);
+ if (parcel && !parcel->objectsCount()) {
+ status_t err = metadata.readFromParcel(parcel);
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Metadata parcel has wrong format");
+ }
+ }
+
status_t err = client->createSurfaceChecked(
- String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid);
+ String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata));
if (err == NAME_NOT_FOUND) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return 0;
@@ -377,6 +387,28 @@
transaction->transferTouchFocus(fromToken, toToken);
}
+static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint id, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ if (!parcel) {
+ jniThrowNullPointerException(env, "attribute data");
+ return;
+ }
+ if (parcel->objectsCount()) {
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Tried to marshall a Parcel that contained Binder objects.");
+ return;
+ }
+
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ std::vector<uint8_t> byteData(parcel->dataSize());
+ memcpy(byteData.data(), parcel->data(), parcel->dataSize());
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setMetadata(ctrl, id, std::move(byteData));
+}
+
static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloatArray fColor) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -981,7 +1013,7 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
- {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J",
+ {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
(void*)nativeCreate },
{"nativeReadFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadFromParcel },
@@ -1099,6 +1131,8 @@
(void*)nativeSetInputWindowInfo },
{"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
(void*)nativeTransferTouchFocus },
+ {"nativeSetMetadata", "(JILandroid/os/Parcel;)V",
+ (void*)nativeSetMetadata },
{"nativeGetDisplayedContentSamplingAttributes",
"(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;",
(void*)nativeGetDisplayedContentSamplingAttributes },
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8681d4b..6ee9606 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -821,7 +821,7 @@
// Utility to close down the Zygote socket file descriptors while
// the child is still running as root with Zygote's privileges. Each
-// descriptor (if any) is closed via dup2(), replacing it with a valid
+// descriptor (if any) is closed via dup3(), replacing it with a valid
// (open) descriptor to /dev/null.
static void DetachDescriptors(JNIEnv* env,
@@ -829,15 +829,15 @@
fail_fn_t fail_fn) {
if (fds_to_close.size() > 0) {
- android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+ android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC));
if (devnull_fd == -1) {
fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
for (int fd : fds_to_close) {
ALOGV("Switching descriptor %d to /dev/null", fd);
- if (dup2(devnull_fd, fd) == -1) {
- fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+ if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) {
+ fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno)));
}
}
}
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 4e48663..4b37f13 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -423,13 +423,13 @@
}
void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
- const int dev_null_fd = open("/dev/null", O_RDWR);
+ const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (dev_null_fd < 0) {
fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- if (dup2(dev_null_fd, fd) == -1) {
- fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+ fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
fd,
strerror(errno)));
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f06165c..7e7942e 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -522,6 +522,8 @@
optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Packages that are whitelisted for ignoring location settings (during emergencies)
+ optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Location location = 69;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a54bd7..25baa92 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -43,7 +43,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
- <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
+ <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
@@ -3329,7 +3329,7 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an application to clear user data.
+ <!-- @SystemApi @TestApi Allows an application to clear user data.
<p>Not for use by third-party applications
@hide
-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 49f2c84..c05795d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2211,6 +2211,10 @@
has expired, then assume the device is receiving insufficient current to charge
effectively and terminate the dream. Use -1 to disable this safety feature. -->
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
+ <!-- Limit of how long the device can remain unlocked due to attention checking. -->
+ <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes -->
+ <!-- How long we should wait until we give up on receiving an attention API callback. -->
+ <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds -->
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d556130..f79e22d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3554,4 +3554,8 @@
<java-symbol type="bool" name="config_cbrs_supported" />
<java-symbol type="bool" name="config_awareSettingAvailable" />
+
+ <!-- For Attention Service -->
+ <java-symbol type="integer" name="config_attentionMaximumExtension" />
+ <java-symbol type="integer" name="config_attentionApiTimeout" />
</resources>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index a15dbc8..bd7f852 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,7 @@
Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4755d45..c9e4694 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1821,6 +1821,8 @@
* @param cct The correlated color temperature, in Kelvin
* @return Corresponding XYZ values
* @throws IllegalArgumentException If cct is invalid
+ *
+ * @hide
*/
@NonNull
@Size(3)
@@ -1851,6 +1853,8 @@
* @param srcWhitePoint The white point to adapt from
* @param dstWhitePoint The white point to adapt to
* @return A 3x3 matrix as a non-null array of 9 floats
+ *
+ * @hide
*/
@NonNull
@Size(9)
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index d2903f0..2f2d575 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -99,7 +99,7 @@
////////////////// Java flags compatibility //////////////////
-/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
+/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
match up with skia anymore, so we have to do some shuffling in get/set flags()
3 flags apply to SkPaint (antialias, dither, filter -> enum)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b7f042b..f996d38 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -30,6 +30,7 @@
import android.app.PendingIntent;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -3989,33 +3990,11 @@
}
/**
- * Indicate A2DP source or sink connection state change.
- * @param device Bluetooth device connected/disconnected
- * @param state new connection state (BluetoothProfile.STATE_xxx)
- * @param profile profile for the A2DP device
- * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
- * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
- * @return a delay in ms that the caller should wait before broadcasting
- * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent.
- * {@hide}
- */
- public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state,
- int profile) {
- final IAudioService service = getService();
- int delay = 0;
- try {
- delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return delay;
- }
-
- /**
* Indicate A2DP source or sink connection state change and eventually suppress
* the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
* @param device Bluetooth device connected/disconnected
- * @param state new connection state (BluetoothProfile.STATE_xxx)
+ * @param state new connection state, {@link BluetoothProfile#STATE_CONNECTED}
+ * or {@link BluetoothProfile#STATE_DISCONNECTED}
* @param profile profile for the A2DP device
* @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting.
* (either {@link android.bluetooth.BluetoothProfile.A2DP} or
@@ -4027,8 +4006,8 @@
* {@hide}
*/
public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- BluetoothDevice device, int state, int profile,
- boolean suppressNoisyIntent, int a2dpVolume) {
+ BluetoothDevice device, int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
final IAudioService service = getService();
int delay = 0;
try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af016d5..ffa3b24 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,6 +39,8 @@
*/
public class AudioSystem
{
+ private static final boolean DEBUG_VOLUME = true;
+
private static final String TAG = "AudioSystem";
/* These values must be kept in sync with system/audio.h */
/*
@@ -879,6 +881,15 @@
}
}
+ /** Wrapper for native methods called from AudioService */
+ public static int setStreamVolumeIndexAS(int stream, int index, int device) {
+ if (DEBUG_VOLUME) {
+ Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
+ + " dev=" + Integer.toHexString(device) + " idx=" + index);
+ }
+ return setStreamVolumeIndex(stream, index, device);
+ }
+
// usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
public static final int SYNC_EVENT_NONE = 0;
public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
@@ -906,7 +917,7 @@
@UnsupportedAppUsage
public static native int initStreamVolume(int stream, int indexMin, int indexMax);
@UnsupportedAppUsage
- public static native int setStreamVolumeIndex(int stream, int index, int device);
+ private static native int setStreamVolumeIndex(int stream, int index, int device);
public static native int getStreamVolumeIndex(int stream, int device);
public static native int setMasterVolume(float value);
public static native float getMasterVolume();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 14bdab9..f5aeca7 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -151,8 +151,6 @@
void setWiredDeviceConnectionState(int type, int state, String address, String name,
String caller);
- int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
-
void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f..33e7d8e 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -335,7 +335,7 @@
private final Uri mPlaylistsUri;
@UnsupportedAppUsage
private final Uri mFilesUri;
- private final Uri mFilesUriNoNotify;
+ private final Uri mFilesFullUri;
private final boolean mProcessPlaylists;
private final boolean mProcessGenres;
private int mMtpObjectHandle;
@@ -445,7 +445,11 @@
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mFilesUri = Files.getContentUri(volumeName);
- mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+
+ Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+ filesFullUri = MediaStore.setIncludePending(filesFullUri);
+ filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
+ mFilesFullUri = filesFullUri;
if (!volumeName.equals("internal")) {
// we only support playlists on external media
@@ -1625,7 +1629,7 @@
try {
where = Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { path };
- c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
+ c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, null, null);
if (c != null && c.moveToFirst()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90..417a427 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -405,6 +405,11 @@
nativeFormat, consumerUsage);
return;
}
+
+ if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+ gbConsumer->setConsumerIsProtected(true);
+ }
+
ctx->setBufferConsumer(bufferConsumer);
bufferConsumer->setName(consumerName);
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5fae9d5..3156732 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -47,32 +47,13 @@
static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
sp<SurfaceComposerClient> client = surfaceControl->getClient();
sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-
- Vector<ui::ColorMode> colorModes;
- status_t err = client->getDisplayColorModes(display, &colorModes);
+ bool isWideColorDisplay = false;
+ status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
if (err) {
ALOGE("unable to get wide color support");
return false;
}
-
- bool wideColorBoardConfig =
- getBool<ISurfaceFlingerConfigs,
- &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
-
- for (android::ui::ColorMode colorMode : colorModes) {
- switch (colorMode) {
- case ui::ColorMode::DISPLAY_P3:
- case ui::ColorMode::ADOBE_RGB:
- case ui::ColorMode::DCI_P3:
- if (wideColorBoardConfig) {
- return true;
- }
- break;
- default:
- break;
- }
- }
- return false;
+ return isWideColorDisplay;
}
static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aff6f04..4f6a4ad 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -822,6 +822,9 @@
dumpSetting(s, p,
Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
+ GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST);
p.end(locationToken);
final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 5fdf76f..b584f67 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.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 @@
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 f3ca938..4f870f6 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 @@
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 a113a63..0f51376 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 @@
- mBubblePadding
+ Math.max(
insets.getSystemWindowInsetLeft(),
- insets.getDisplayCutout().getSafeInsetLeft());
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetLeft()
+ : 0);
mAllowableStackPositionRegion.right =
mLayout.getWidth()
- mIndividualBubbleSize
@@ -219,20 +221,26 @@
- 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 0f686df..db819d5 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 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 @@
}
@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 @@
}
@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/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index d09823e..a5a515f 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -34,11 +34,11 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.Preconditions;
@@ -48,6 +48,9 @@
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* Find the best Service, and bind to it.
@@ -61,16 +64,20 @@
public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
- /**
- * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
- */
+
+ /** Function to run on binder interface. */
public interface BinderRunner {
- /**
- * Runs on the retrieved binder.
- *
- * @param binder the binder retrieved from the {@link ServiceWatcher}.
- */
- void run(IBinder binder);
+ /** Called to run client code with the binder. */
+ void run(IBinder binder) throws RemoteException;
+ }
+
+ /**
+ * Function to run on binder interface.
+ * @param <T> Type to return.
+ */
+ public interface BlockingBinderRunner<T> {
+ /** Called to run client code with the binder. */
+ T run(IBinder binder) throws RemoteException;
}
public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
@@ -120,18 +127,14 @@
private final Handler mHandler;
- // this lock is held to ensure the service binder is not exposed (via runOnBinder) until after
- // the new service initialization work has completed
- private final Object mBindLock = new Object();
-
// read/write from handler thread
+ private IBinder mBestService;
private int mCurrentUserId;
// read from any thread, write from handler thread
private volatile ComponentName mBestComponent;
private volatile int mBestVersion;
private volatile int mBestUserId;
- private volatile IBinder mBestService;
public ServiceWatcher(Context context, String logTag, String action,
int overlaySwitchResId, int defaultServicePackageNameResId,
@@ -163,17 +166,9 @@
mBestService = null;
}
- // called on handler thread
- @GuardedBy("mBindLock")
- protected void onBind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- }
+ protected void onBind() {}
- // called on handler thread
- @GuardedBy("mBindLock")
- protected void onUnbind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- }
+ protected void onUnbind() {}
/**
* Start this watcher, including binding to the current best match and
@@ -248,25 +243,6 @@
return bestComponent == null ? null : bestComponent.getPackageName();
}
- /**
- * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run
- * serially.
- */
- public final void runOnBinder(BinderRunner runner) {
- synchronized (mBindLock) {
- IBinder service = mBestService;
- if (service != null) {
- try {
- runner.run(service);
- } catch (Exception e) {
- // remote exceptions cannot be allowed to crash system server
- Log.e(TAG, "exception while while running " + runner + " on " + service
- + " from " + this, e);
- }
- }
- }
- }
-
private boolean isServiceMissing() {
return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction),
PackageManager.MATCH_DIRECT_BOOT_AWARE
@@ -380,28 +356,66 @@
mBestUserId = UserHandle.USER_NULL;
}
+ /**
+ * Runs the given function asynchronously if currently connected. Suppresses any RemoteException
+ * thrown during execution.
+ */
+ public final void runOnBinder(BinderRunner runner) {
+ runOnHandler(() -> {
+ if (mBestService == null) {
+ return;
+ }
+
+ try {
+ runner.run(mBestService);
+ } catch (RuntimeException e) {
+ // the code being run is privileged, but may be outside the system server, and thus
+ // we cannot allow runtime exceptions to crash the system server
+ Log.e(TAG, "exception while while running " + runner + " on " + mBestService
+ + " from " + this, e);
+ } catch (RemoteException e) {
+ // do nothing
+ }
+ });
+ }
+
+ /**
+ * Runs the given function synchronously if currently connected, and returns the default value
+ * if not currently connected or if any exception is thrown.
+ */
+ public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
+ try {
+ return runOnHandlerBlocking(() -> {
+ if (mBestService == null) {
+ return defaultValue;
+ }
+
+ try {
+ return runner.run(mBestService);
+ } catch (RemoteException e) {
+ return defaultValue;
+ }
+ });
+ } catch (InterruptedException e) {
+ return defaultValue;
+ }
+ }
+
@Override
public final void onServiceConnected(ComponentName component, IBinder binder) {
- mHandler.post(() -> {
+ runOnHandler(() -> {
if (D) Log.d(mTag, component + " connected");
-
- // hold the lock so that mBestService cannot be used by runOnBinder until complete
- synchronized (mBindLock) {
- mBestService = binder;
- onBind();
- }
+ mBestService = binder;
+ onBind();
});
}
@Override
public final void onServiceDisconnected(ComponentName component) {
- mHandler.post(() -> {
+ runOnHandler(() -> {
if (D) Log.d(mTag, component + " disconnected");
-
mBestService = null;
- synchronized (mBindLock) {
- onUnbind();
- }
+ onUnbind();
});
}
@@ -410,4 +424,32 @@
ComponentName bestComponent = mBestComponent;
return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion;
}
+
+ private void runOnHandler(Runnable r) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ r.run();
+ } else {
+ mHandler.post(r);
+ }
+ }
+
+ private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ try {
+ return c.call();
+ } catch (Exception e) {
+ // Function cannot throw exception, this should never happen
+ throw new IllegalStateException(e);
+ }
+ } else {
+ FutureTask<T> task = new FutureTask<>(c);
+ mHandler.post(task);
+ try {
+ return task.get();
+ } catch (ExecutionException e) {
+ // Function cannot throw exception, this should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a376e7a..0890032 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.job.JobProtoEnums;
import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
import android.os.health.UidHealthStats;
+import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.SignalStrength;
@@ -1651,4 +1653,23 @@
return new HealthStatsParceler(uidWriter);
}
+ /**
+ * Delay for sending ACTION_CHARGING after device is plugged in.
+ *
+ * @hide
+ */
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null);
+ final long ident = Binder.clearCallingIdentity();
+
+ try {
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ return Settings.Global.putLong(contentResolver,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+ delayMillis);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 27edbbf..b71a751 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -174,10 +174,11 @@
@Override
public void onSuccess(int requestCode, int result, long timestamp) {
callback.onSuccess(requestCode, result, timestamp);
- userState.mAttentionCheckCache = new AttentionCheckCache(
- SystemClock.uptimeMillis(), result,
- timestamp);
-
+ synchronized (mLock) {
+ userState.mAttentionCheckCache = new AttentionCheckCache(
+ SystemClock.uptimeMillis(), result,
+ timestamp);
+ }
StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
result);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
new file mode 100644
index 0000000..d652f93
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/** @hide */
+/*package*/ final class AudioDeviceBroker {
+
+ private static final String TAG = "AudioDeviceBroker";
+
+ private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
+
+ /*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000;
+ // Timeout for connection to bluetooth headset service
+ /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
+ private final @NonNull AudioService mAudioService;
+ private final @NonNull Context mContext;
+
+ /** Forced device usage for communications sent to AudioSystem */
+ private int mForcedUseForComm;
+ /**
+ * Externally reported force device usage state returned by getters: always consistent
+ * with requests by setters */
+ private int mForcedUseForCommExt;
+
+ // Manages all connected devices, only ever accessed on the message loop
+ //### or make it synchronized
+ private final AudioDeviceInventory mDeviceInventory;
+ // Manages notifications to BT service
+ private final BtHelper mBtHelper;
+
+
+ //-------------------------------------------------------------------
+ private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+ private static long sLastDeviceConnectMsgTime = 0;
+
+ private final Object mBluetoothA2dpEnabledLock = new Object();
+ // Request to override default use of A2DP for media.
+ @GuardedBy("mBluetoothA2dpEnabledLock")
+ private boolean mBluetoothA2dpEnabled;
+
+ // lock always taken synchronized on mConnectedDevices
+ /*package*/ final Object mA2dpAvrcpLock = new Object();
+ // lock always taken synchronized on mConnectedDevices
+ /*package*/ final Object mHearingAidLock = new Object();
+
+ // lock always taken when accessing AudioService.mSetModeDeathHandlers
+ /*package*/ final Object mSetModeLock = new Object();
+
+ //-------------------------------------------------------------------
+ /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+ mContext = context;
+ mAudioService = service;
+ setupMessaging(context);
+ mBtHelper = new BtHelper(this);
+ mDeviceInventory = new AudioDeviceInventory(this);
+
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ mForcedUseForCommExt = mForcedUseForComm;
+
+ }
+
+ /*package*/ Context getContext() {
+ return mContext;
+ }
+
+ //---------------------------------------------------------------------
+ // Communication from AudioService
+ // All methods are asynchronous and never block
+ // All permission checks are done in AudioService, all incoming calls are considered "safe"
+ // All post* methods are asynchronous
+
+ /*package*/ void onSystemReady() {
+ mBtHelper.onSystemReady();
+ }
+
+ /*package*/ void onAudioServerDied() {
+ // Restore forced usage for communications and record
+ onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
+ onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+ // restore devices
+ sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
+ }
+
+ /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ useCase, config, eventSource);
+ }
+
+ /*package*/ void toggleHdmiIfConnected_Async() {
+ sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+ }
+
+ /*package*/ void disconnectAllBluetoothProfiles() {
+ mBtHelper.disconnectAllBluetoothProfiles();
+ }
+
+ /**
+ * Handle BluetoothHeadset intents where the action is one of
+ * {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
+ * {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
+ * @param intent
+ */
+ /*package*/ void receiveBtEvent(@NonNull Intent intent) {
+ mBtHelper.receiveBtEvent(intent);
+ }
+
+ /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ if (mBluetoothA2dpEnabled == on) {
+ return;
+ }
+ mBluetoothA2dpEnabled = on;
+ mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ source);
+ }
+ }
+
+ /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) {
+ if (on) {
+ if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+ }
+ mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+
+ mForcedUseForCommExt = mForcedUseForComm;
+ setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ }
+
+ /*package*/ boolean isSpeakerphoneOn() {
+ return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+ }
+
+ /*package*/ void setWiredDeviceConnectionState(int type,
+ @AudioService.ConnectionState int state, String address, String name,
+ String caller) {
+ //TODO move logging here just like in setBluetooth* methods
+ mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+ }
+
+ /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
+ // only querying address as this is the only readily available field
+ // on the device
+ + " addr=" + device.getAddress()
+ + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
+ + " vol=" + a2dpVolume)).printLog(TAG));
+ if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+ new BtHelper.BluetoothA2dpDeviceInfo(device))) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP connection state ignored"));
+ return 0;
+ }
+ return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+ device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+ }
+
+ /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+ @NonNull BluetoothDevice device,
+ @AudioService.BtProfileConnectionState int state, int profile,
+ boolean suppressNoisyIntent, int a2dpVolume) {
+ return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+ suppressNoisyIntent, a2dpVolume);
+ }
+
+ /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "setHearingAidDeviceConnectionState state=" + state
+ + " addr=" + device.getAddress()
+ + " supprNoisy=" + suppressNoisyIntent
+ + " src=" + eventSource)).printLog(TAG));
+ return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+ device, state, suppressNoisyIntent, musicDevice);
+ }
+
+ // never called by system components
+ /*package*/ void setBluetoothScoOnByApp(boolean on) {
+ mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+ }
+
+ /*package*/ boolean isBluetoothScoOnForApp() {
+ return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+ }
+
+ /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
+ //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+ if (on) {
+ // do not accept SCO ON if SCO audio is not connected
+ if (!mBtHelper.isBluetoothScoOn()) {
+ mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+ return;
+ }
+ mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+ mForcedUseForCommExt = mForcedUseForComm;
+ AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+ // Un-mute ringtone stream volume
+ mAudioService.setUpdateRingerModeServiceInt();
+ }
+
+ /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+ return mDeviceInventory.startWatchingRoutes(observer);
+ }
+
+ /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+ return mDeviceInventory.getCurAudioRoutes();
+ }
+
+ /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+ synchronized (mA2dpAvrcpLock) {
+ return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+ }
+ }
+
+ /*package*/ boolean isBluetoothA2dpOn() {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
+ }
+
+ /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
+ sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+ }
+
+ /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
+ sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+ }
+
+ /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
+ sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+ }
+
+ /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
+ sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+ }
+
+ /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+ @NonNull String eventSource) {
+ mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
+ }
+
+ /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
+ mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+ }
+
+ //---------------------------------------------------------------------
+ // Communication with (to) AudioService
+ //TODO check whether the AudioService methods are candidates to move here
+ /*package*/ void postAccessoryPlugMediaUnmute(int device) {
+ mAudioService.postAccessoryPlugMediaUnmute(device);
+ }
+
+ /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) {
+ return mAudioService.getStreamState(streamType);
+ }
+
+ /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() {
+ return mAudioService.mSetModeDeathHandlers;
+ }
+
+ /*package*/ int getDeviceForStream(int streamType) {
+ return mAudioService.getDeviceForStream(streamType);
+ }
+
+ /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) {
+ mAudioService.setDeviceVolume(streamState, device);
+ }
+
+ /*packages*/ void observeDevicesForAllStreams() {
+ mAudioService.observeDevicesForAllStreams();
+ }
+
+ /*package*/ boolean isInCommunication() {
+ return mAudioService.isInCommunication();
+ }
+
+ /*package*/ boolean hasMediaDynamicPolicy() {
+ return mAudioService.hasMediaDynamicPolicy();
+ }
+
+ /*package*/ ContentResolver getContentResolver() {
+ return mAudioService.getContentResolver();
+ }
+
+ /*package*/ void checkMusicActive(int deviceType, String caller) {
+ mAudioService.checkMusicActive(deviceType, caller);
+ }
+
+ /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+ mAudioService.checkVolumeCecOnHdmiConnection(state, caller);
+ }
+
+ //---------------------------------------------------------------------
+ // Message handling on behalf of helper classes
+ /*package*/ void broadcastScoConnectionState(int state) {
+ sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+ }
+
+ /*package*/ void broadcastBecomingNoisy() {
+ sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+ }
+
+ //###TODO unify with handleSetA2dpSinkConnectionState
+ /*package*/ void postA2dpSinkConnection(int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+ sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ //###TODO unify with handleSetA2dpSourceConnectionState
+ /*package*/ void postA2dpSourceConnection(int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+ sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ /*package*/ void postSetWiredDeviceConnectionState(
+ AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
+ sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
+ }
+
+ /*package*/ void postSetHearingAidConnectionState(
+ @AudioService.BtProfileConnectionState int state,
+ @NonNull BluetoothDevice device, int delay) {
+ sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+ state,
+ device,
+ delay);
+ }
+
+ //---------------------------------------------------------------------
+ // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
+ // only call from a "handle"* method or "on"* method
+
+ // Handles request to override default use of A2DP for media.
+ //@GuardedBy("mConnectedDevices")
+ /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) {
+ // for logging only
+ final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ onSetForceUse(
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ eventSource);
+ }
+ }
+
+ /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+ String deviceName) {
+ return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+ }
+
+ /*package*/ void handleDisconnectA2dp() {
+ mDeviceInventory.disconnectA2dp();
+ }
+ /*package*/ void handleDisconnectA2dpSink() {
+ mDeviceInventory.disconnectA2dpSink();
+ }
+
+ /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version?
+ // might be ok here because called on BT thread? + sync happening in
+ // checkSendBecomingNoisyIntent
+ final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
+ AudioSystem.DEVICE_NONE);
+ final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
+ + " state= " + state
+ + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
+ }
+ sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ /*package*/ void handleDisconnectHearingAid() {
+ mDeviceInventory.disconnectHearingAid();
+ }
+
+ /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+ btDeviceInfo);
+ }
+
+ /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
+ sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+ }
+
+ /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
+ mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+ }
+
+ /*package*/ void postReportNewRoutes() {
+ sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+ }
+
+ /*package*/ void cancelA2dpDockTimeout() {
+ mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+ }
+
+ //###
+ // must be called synchronized on mConnectedDevices
+ /*package*/ boolean hasScheduledA2dpDockTimeout() {
+ return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ //###
+ // must be called synchronized on mConnectedDevices
+ /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
+ return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
+ }
+
+ /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
+ sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+ }
+
+ /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+ synchronized (mA2dpAvrcpLock) {
+ mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+ }
+ }
+
+ /*package*/ boolean getBluetoothA2dpEnabled() {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
+ }
+
+ /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+ synchronized (mA2dpAvrcpLock) {
+ return mBtHelper.getA2dpCodec(device);
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // Internal handling of messages
+ // These methods are ALL synchronous, in response to message handling in BrokerHandler
+ // Blocking in any of those will block the message queue
+
+ private void onSetForceUse(int useCase, int config, String eventSource) {
+ if (useCase == AudioSystem.FOR_MEDIA) {
+ postReportNewRoutes();
+ }
+ AudioService.sForceUseLogger.log(
+ new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+ AudioSystem.setForceUse(useCase, config);
+ }
+
+ private void onSendBecomingNoisyIntent() {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
+ sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ }
+
+ //---------------------------------------------------------------------
+ // Message handling
+ private BrokerHandler mBrokerHandler;
+ private BrokerThread mBrokerThread;
+ private PowerManager.WakeLock mBrokerEventWakeLock;
+
+ private void setupMessaging(Context ctxt) {
+ final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE);
+ mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "handleAudioDeviceEvent");
+ mBrokerThread = new BrokerThread();
+ mBrokerThread.start();
+ waitForBrokerHandlerCreation();
+ }
+
+ private void waitForBrokerHandlerCreation() {
+ synchronized (this) {
+ while (mBrokerHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interruption while waiting on BrokerHandler");
+ }
+ }
+ }
+ }
+
+ /** Class that handles the device broker's message queue */
+ private class BrokerThread extends Thread {
+ BrokerThread() {
+ super("AudioDeviceBroker");
+ }
+
+ @Override
+ public void run() {
+ // Set this thread up so the handler will work on it
+ Looper.prepare();
+
+ synchronized (AudioDeviceBroker.this) {
+ mBrokerHandler = new BrokerHandler();
+
+ // Notify that the handler has been created
+ AudioDeviceBroker.this.notify();
+ }
+
+ Looper.loop();
+ }
+ }
+
+ /** Class that handles the message queue */
+ private class BrokerHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESTORE_DEVICES:
+ mDeviceInventory.onRestoreDevices();
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBtHelper.onAudioServerDiedRestoreA2dp();
+ }
+ break;
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ mDeviceInventory.onSetWiredDeviceConnectionState(
+ (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+ break;
+ case MSG_I_BROADCAST_BT_CONNECTION_STATE:
+ mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+ break;
+ case MSG_IIL_SET_FORCE_USE: // intented fall-through
+ case MSG_IIL_SET_FORCE_BT_A2DP_USE:
+ onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
+ break;
+ case MSG_REPORT_NEW_ROUTES:
+ mDeviceInventory.onReportNewRoutes();
+ break;
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ mDeviceInventory.onSetA2dpSinkConnectionState(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ mDeviceInventory.onSetA2dpSourceConnectionState(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+ break;
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ mDeviceInventory.onSetHearingAidConnectionState(
+ (BluetoothDevice) msg.obj, msg.arg1);
+ break;
+ case MSG_BT_HEADSET_CNCT_FAILED:
+ mBtHelper.resetBluetoothSco();
+ break;
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ // msg.obj == address of BTA2DP device
+ mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+ break;
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ final int a2dpCodec;
+ final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+ synchronized (mA2dpAvrcpLock) {
+ a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+ }
+ mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
+ break;
+ case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
+ onSendBecomingNoisyIntent();
+ break;
+ case MSG_II_SET_HEARING_AID_VOLUME:
+ mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+ break;
+ case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
+ mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+ break;
+ case MSG_I_DISCONNECT_BT_SCO:
+ mBtHelper.disconnectBluetoothSco(msg.arg1);
+ break;
+ case MSG_TOGGLE_HDMI:
+ mDeviceInventory.onToggleHdmi();
+ break;
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+ break;
+ default:
+ Log.wtf(TAG, "Invalid message " + msg.what);
+ }
+ if (isMessageHandledUnderWakelock(msg.what)) {
+ try {
+ mBrokerEventWakeLock.release();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception releasing wakelock", e);
+ }
+ }
+ }
+ }
+
+ // List of all messages. If a message has be handled under wakelock, add it to
+ // the isMessageHandledUnderWakelock(int) method
+ // Naming of msg indicates arguments, using JNI argument grammar
+ // (e.g. II indicates two int args, IL indicates int and Obj arg)
+ private static final int MSG_RESTORE_DEVICES = 1;
+ private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
+ private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
+ private static final int MSG_IIL_SET_FORCE_USE = 4;
+ private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+ private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
+ private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
+ private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+ private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+ private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+ private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
+ private static final int MSG_REPORT_NEW_ROUTES = 13;
+ private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
+ private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
+ private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+ private static final int MSG_TOGGLE_HDMI = 17;
+ private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
+
+
+ private static boolean isMessageHandledUnderWakelock(int msgId) {
+ switch(msgId) {
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_TOGGLE_HDMI:
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Message helper methods
+
+ // sendMsg() flags
+ /** If the msg is already queued, replace it with this one. */
+ private static final int SENDMSG_REPLACE = 0;
+ /** If the msg is already queued, ignore this one and leave the old. */
+ private static final int SENDMSG_NOOP = 1;
+ /** If the msg is already queued, queue this one and leave the old. */
+ private static final int SENDMSG_QUEUE = 2;
+
+ private void sendMsg(int msg, int existingMsgPolicy, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
+ }
+
+ private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
+ }
+
+ private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
+ }
+
+ private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
+ }
+
+ private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
+ }
+
+ private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
+ }
+
+ private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
+ sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
+ }
+
+ private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
+ }
+
+ private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
+ }
+
+ private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
+ }
+
+ private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
+ int delay) {
+ if (existingMsgPolicy == SENDMSG_REPLACE) {
+ mBrokerHandler.removeMessages(msg);
+ } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) {
+ return;
+ }
+
+ if (isMessageHandledUnderWakelock(msg)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception acquiring wakelock", e);
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ synchronized (sLastDeviceConnectionMsgTimeLock) {
+ long time = SystemClock.uptimeMillis() + delay;
+
+ switch (msg) {
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ if (sLastDeviceConnectMsgTime >= time) {
+ // add a little delay to make sure messages are ordered as expected
+ time = sLastDeviceConnectMsgTime + 30;
+ }
+ sLastDeviceConnectMsgTime = time;
+ break;
+ default:
+ break;
+ }
+
+ mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+ time);
+ }
+ }
+
+ //-------------------------------------------------------------
+ // internal utilities
+ private void sendBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
new file mode 100644
index 0000000..eb76e6e0
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioDevicePort;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPort;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * Class to manage the inventory of all connected devices.
+ * This class is thread-safe.
+ */
+public final class AudioDeviceInventory {
+
+ private static final String TAG = "AS.AudioDeviceInventory";
+
+ // Actual list of connected devices
+ // Key for map created from DeviceInfo.makeDeviceListKey()
+ private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
+
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
+ // cache of the address of the last dock the device was connected to
+ private String mDockAddress;
+
+ // Monitoring of audio routes. Protected by mAudioRoutes.
+ final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
+ final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
+ new RemoteCallbackList<IAudioRoutesObserver>();
+
+ //------------------------------------------------------------
+ /**
+ * Class to store info about connected devices.
+ * Use makeDeviceListKey() to make a unique key for this list.
+ */
+ private static class DeviceInfo {
+ final int mDeviceType;
+ final String mDeviceName;
+ final String mDeviceAddress;
+ int mDeviceCodecFormat;
+
+ DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+ mDeviceType = deviceType;
+ mDeviceName = deviceName;
+ mDeviceAddress = deviceAddress;
+ mDeviceCodecFormat = deviceCodecFormat;
+ }
+
+ @Override
+ public String toString() {
+ return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+ + " name:" + mDeviceName
+ + " addr:" + mDeviceAddress
+ + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+ }
+
+ /**
+ * Generate a unique key for the mConnectedDevices List by composing the device "type"
+ * and the "address" associated with a specific instance of that device type
+ */
+ private static String makeDeviceListKey(int device, String deviceAddress) {
+ return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
+ }
+ }
+
+ /**
+ * A class just for packaging up a set of connection parameters.
+ */
+ /*package*/ class WiredDeviceConnectionState {
+ public final int mType;
+ public final @AudioService.ConnectionState int mState;
+ public final String mAddress;
+ public final String mName;
+ public final String mCaller;
+
+ /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+ String address, String name, String caller) {
+ mType = type;
+ mState = state;
+ mAddress = address;
+ mName = name;
+ mCaller = caller;
+ }
+ }
+
+ //------------------------------------------------------------
+ // Message handling from AudioDeviceBroker
+
+ /**
+ * Restore previously connected devices. Use in case of audio server crash
+ * (see AudioService.onAudioServerDied() method)
+ */
+ /*package*/ void onRestoreDevices() {
+ synchronized (mConnectedDevices) {
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceInfo di = mConnectedDevices.valueAt(i);
+ AudioSystem.setDeviceConnectionState(
+ di.mDeviceType,
+ AudioSystem.DEVICE_STATE_AVAILABLE,
+ di.mDeviceAddress,
+ di.mDeviceName,
+ di.mDeviceCodecFormat);
+ }
+ }
+ }
+
+ /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
+ @AudioService.BtProfileConnectionState int state, int a2dpVolume) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
+ + state + " is dock=" + btDevice.isBluetoothDock());
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP sink connected: device addr=" + address + " state=" + state));
+
+ final int a2dpCodec;
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ a2dpCodec = btInfo.getCodec();
+ }
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ btDevice.getAddress());
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ // introduction of a delay for transient disconnections of docks when
+ // power is rapidly turned off/on, this message will be canceled if
+ // we reconnect the dock under a preset delay
+ makeA2dpDeviceUnavailableLater(address,
+ AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
+ // the next time isConnected is evaluated, it will be false for the dock
+ }
+ } else {
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ }
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ // this could be a reconnection after a transient disconnection
+ mDeviceBroker.cancelA2dpDockTimeout();
+ mDockAddress = address;
+ } else {
+ // this could be a connection of another A2DP device before the timeout of
+ // a dock: cancel the dock timeout, and make the dock unavailable now
+ if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
+ mDeviceBroker.cancelA2dpDockTimeout();
+ makeA2dpDeviceUnavailableNow(mDockAddress,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+ }
+ if (a2dpVolume != -1) {
+ AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ // Convert index to internal representation in VolumeStreamState
+ a2dpVolume = a2dpVolume * 10;
+ streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ "onSetA2dpSinkConnectionState");
+ mDeviceBroker.setDeviceVolume(
+ streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ }
+ makeA2dpDeviceAvailable(address, btDevice.getName(),
+ "onSetA2dpSinkConnectionState", a2dpCodec);
+ }
+ }
+ }
+
+ /*package*/ void onSetA2dpSourceConnectionState(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
+ + state);
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcUnavailable(address);
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcAvailable(address);
+ }
+ }
+ }
+
+ /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
+ @AudioService.BtProfileConnectionState int state) {
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onSetHearingAidConnectionState addr=" + address));
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
+ btDevice.getAddress());
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ makeHearingAidDeviceUnavailable(address);
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ makeHearingAidDeviceAvailable(address, btDevice.getName(),
+ "onSetHearingAidConnectionState");
+ }
+ }
+ }
+
+ /*package*/ void onBluetoothA2dpDeviceConfigChange(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+ }
+ if (btDevice == null) {
+ return;
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onBluetoothA2dpDeviceConfigChange addr=" + address));
+
+ final int a2dpCodec = btInfo.getCodec();
+
+ synchronized (mConnectedDevices) {
+ if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2dp config change ignored"));
+ return;
+ }
+ final String key =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+ return;
+ }
+ // Device is connected
+ if (di.mDeviceCodecFormat != a2dpCodec) {
+ di.mDeviceCodecFormat = a2dpCodec;
+ mConnectedDevices.replace(key, di);
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
+ + di.mDeviceCodecFormat);
+ }
+ if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) {
+ // force A2DP device disconnection in case of error so that AudioService state
+ // is consistent with audio policy manager state
+ final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ setBluetoothA2dpDeviceConnectionState(
+ btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+ false /* suppressNoisyIntent */, musicDevice,
+ -1 /* a2dpVolume */);
+ }
+ }
+ }
+
+ /*package*/ void onBluetoothA2dpActiveDeviceChange(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ int a2dpVolume = btInfo.getVolume();
+ final int a2dpCodec = btInfo.getCodec();
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onBluetoothA2dpActiveDeviceChange addr=" + address));
+
+ synchronized (mConnectedDevices) {
+ //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
+ // for this type of message
+ if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2dp config change ignored"));
+ return;
+ }
+ final String key = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ return;
+ }
+
+ // Device is connected
+ if (a2dpVolume != -1) {
+ final AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ // Convert index to internal representation in VolumeStreamState
+ a2dpVolume = a2dpVolume * 10;
+ streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ "onBluetoothA2dpActiveDeviceChange");
+ mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ }
+
+ if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+ int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ // force A2DP device disconnection in case of error so that AudioService state is
+ // consistent with audio policy manager state
+ setBluetoothA2dpDeviceConnectionState(
+ btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+ false /* suppressNoisyIntent */, musicDevice,
+ -1 /* a2dpVolume */);
+ }
+ }
+ }
+
+ /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+ synchronized (mConnectedDevices) {
+ makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+ }
+ }
+
+ /*package*/ void onReportNewRoutes() {
+ int n = mRoutesObservers.beginBroadcast();
+ if (n > 0) {
+ AudioRoutesInfo routes;
+ synchronized (mCurAudioRoutes) {
+ routes = new AudioRoutesInfo(mCurAudioRoutes);
+ }
+ while (n > 0) {
+ n--;
+ IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
+ try {
+ obs.dispatchAudioRoutesChanged(routes);
+ } catch (RemoteException e) { }
+ }
+ }
+ mRoutesObservers.finishBroadcast();
+ mDeviceBroker.observeDevicesForAllStreams();
+ }
+
+ private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
+
+ /*package*/ void onSetWiredDeviceConnectionState(
+ AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
+ AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+
+ synchronized (mConnectedDevices) {
+ if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
+ && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
+ mDeviceBroker.setBluetoothA2dpOnInt(true,
+ "onSetWiredDeviceConnectionState state DISCONNECTED");
+ }
+
+ if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
+ wdcs.mName)) {
+ // change of connection state failed, bailout
+ return;
+ }
+ if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
+ if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
+ mDeviceBroker.setBluetoothA2dpOnInt(false,
+ "onSetWiredDeviceConnectionState state not DISCONNECTED");
+ }
+ mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
+ }
+ mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+ sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
+ updateAudioRoutes(wdcs.mType, wdcs.mState);
+ }
+ }
+
+ /*package*/ void onToggleHdmi() {
+ synchronized (mConnectedDevices) {
+ // Is HDMI connected?
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
+ return;
+ }
+ // Toggle HDMI to retrigger broadcast with proper formats.
+ setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
+ "android"); // disconnect
+ setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+ AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
+ "android"); // reconnect
+ }
+ }
+ //------------------------------------------------------------
+ //
+
+ /**
+ * Implements the communication with AudioSystem to (dis)connect a device in the native layers
+ * @param connect true if connection
+ * @param device the device type
+ * @param address the address of the device
+ * @param deviceName human-readable name of device
+ * @return false if an error was reported by AudioSystem
+ */
+ /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+ String deviceName) {
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ + Integer.toHexString(device) + " address:" + address
+ + " name:" + deviceName + ")");
+ }
+ synchronized (mConnectedDevices) {
+ final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceKey:" + deviceKey);
+ }
+ DeviceInfo di = mConnectedDevices.get(deviceKey);
+ boolean isConnected = di != null;
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
+ }
+ if (connect && !isConnected) {
+ final int res = AudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
+ + " due to command error " + res);
+ return false;
+ }
+ mConnectedDevices.put(deviceKey, new DeviceInfo(
+ device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(device);
+ return true;
+ } else if (!connect && isConnected) {
+ AudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ // always remove even if disconnection failed
+ mConnectedDevices.remove(deviceKey);
+ return true;
+ }
+ Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+ + ", deviceSpec=" + di + ", connect=" + connect);
+ }
+ return false;
+ }
+
+
+ /*package*/ void disconnectA2dp() {
+ synchronized (mConnectedDevices) {
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ if (toRemove.size() > 0) {
+ final int delay = checkSendBecomingNoisyIntentInt(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ 0, AudioSystem.DEVICE_NONE);
+ toRemove.stream().forEach(deviceAddress ->
+ makeA2dpDeviceUnavailableLater(deviceAddress, delay)
+ );
+ }
+ }
+ }
+ }
+
+ /*package*/ void disconnectA2dpSink() {
+ synchronized (mConnectedDevices) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
+ }
+ }
+
+ /*package*/ void disconnectHearingAid() {
+ synchronized (mConnectedDevices) {
+ synchronized (mDeviceBroker.mHearingAidLock) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_OUT_HEARING_AID devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ if (toRemove.size() > 0) {
+ final int delay = checkSendBecomingNoisyIntentInt(
+ AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+ toRemove.stream().forEach(deviceAddress ->
+ // TODO delay not used?
+ makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+ );
+ }
+ }
+ }
+ }
+
+ // must be called before removing the device from mConnectedDevices
+ // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+ // from AudioSystem
+ /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
+ synchronized (mConnectedDevices) {
+ return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
+ }
+ }
+
+ /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+ synchronized (mCurAudioRoutes) {
+ AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
+ mRoutesObservers.register(observer);
+ return routes;
+ }
+ }
+
+ /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+ return mCurAudioRoutes;
+ }
+
+ /*package*/ int setBluetoothA2dpDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
+ int delay;
+ if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+ throw new IllegalArgumentException("invalid profile " + profile);
+ }
+ synchronized (mConnectedDevices) {
+ if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
+ int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ intState, musicDevice);
+ } else {
+ delay = 0;
+ }
+
+ final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
+ + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
+ + " suppressNoisyIntent: " + suppressNoisyIntent);
+ }
+
+ final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
+ new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
+ if (profile == BluetoothProfile.A2DP) {
+ mDeviceBroker.postA2dpSinkConnection(state,
+ a2dpDeviceInfo,
+ delay);
+ } else { //profile == BluetoothProfile.A2DP_SINK
+ mDeviceBroker.postA2dpSourceConnection(state,
+ a2dpDeviceInfo,
+ delay);
+ }
+ }
+ return delay;
+ }
+
+ /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+ @NonNull BluetoothDevice device,
+ @AudioService.BtProfileConnectionState int state, int profile,
+ boolean suppressNoisyIntent, int a2dpVolume) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ return setBluetoothA2dpDeviceConnectionState(device, state, profile,
+ suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+ }
+ // state == BluetoothProfile.STATE_CONNECTED
+ synchronized (mConnectedDevices) {
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i);
+ if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ continue;
+ }
+ // If A2DP device exists, this is either an active device change or
+ // device config change
+ final String existingDevicekey = mConnectedDevices.keyAt(i);
+ final String deviceName = device.getName();
+ final String address = device.getAddress();
+ final String newDeviceKey = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+ // Device not equal to existing device, active device change
+ if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
+ mConnectedDevices.remove(existingDevicekey);
+ mConnectedDevices.put(newDeviceKey, new DeviceInfo(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
+ address, a2dpCodec));
+ mDeviceBroker.postA2dpActiveDeviceChange(
+ new BtHelper.BluetoothA2dpDeviceInfo(
+ device, a2dpVolume, a2dpCodec));
+ return 0;
+ } else {
+ // Device config change for existing device
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+ return 0;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+ String address, String name, String caller) {
+ synchronized (mConnectedDevices) {
+ int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
+ mDeviceBroker.postSetWiredDeviceConnectionState(
+ new WiredDeviceConnectionState(type, state, address, name, caller),
+ delay);
+ return delay;
+ }
+ }
+
+ /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice) {
+ int delay;
+ synchronized (mConnectedDevices) {
+ if (!suppressNoisyIntent) {
+ int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
+ delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
+ intState, musicDevice);
+ } else {
+ delay = 0;
+ }
+ mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
+ return delay;
+ }
+ }
+
+
+ //-------------------------------------------------------------------
+ // Internal utilities
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
+ int a2dpCodec) {
+ // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
+ // audio policy manager
+ AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+ // Reset A2DP suspend state each time a new sink is connected
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+ new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+ address, a2dpCodec));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ setCurrentAudioRouteNameIfPossible(name);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+ if (address == null) {
+ return;
+ }
+ mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ // Remove A2DP routes as well
+ setCurrentAudioRouteNameIfPossible(null);
+ if (mDockAddress == address) {
+ mDockAddress = null;
+ }
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
+ // prevent any activity on the A2DP audio output to avoid unwanted
+ // reconnection of the sink.
+ AudioSystem.setParameters("A2dpSuspended=true");
+ // retrieve DeviceInfo before removing device
+ final String deviceKey =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
+ final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
+ AudioSystem.AUDIO_FORMAT_DEFAULT;
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(deviceKey);
+ // send the delayed message to make the device unavailable later
+ mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+ }
+
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpSrcAvailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
+ address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpSrcUnavailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
+ final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC)
+ .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
+ mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
+
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
+ new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
+ address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
+ mDeviceBroker.setDeviceVolume(
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC),
+ AudioSystem.DEVICE_OUT_HEARING_AID);
+ setCurrentAudioRouteNameIfPossible(name);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeHearingAidDeviceUnavailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
+ // Remove Hearing Aid routes as well
+ setCurrentAudioRouteNameIfPossible(null);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void setCurrentAudioRouteNameIfPossible(String name) {
+ synchronized (mCurAudioRoutes) {
+ if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
+ return;
+ }
+ if (name != null || !isCurrentDeviceConnected()) {
+ mCurAudioRoutes.bluetoothName = name;
+ mDeviceBroker.postReportNewRoutes();
+ }
+ }
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private boolean isCurrentDeviceConnected() {
+ return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
+ TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
+ }
+
+ // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
+ // sent if:
+ // - none of these devices are connected anymore after one is disconnected AND
+ // - the device being disconnected is actually used for music.
+ // Access synchronized on mConnectedDevices
+ private int mBecomingNoisyIntentDevices =
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
+ | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
+ | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+ | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
+ | AudioSystem.DEVICE_OUT_HEARING_AID;
+
+ // must be called before removing the device from mConnectedDevices
+ // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+ // from AudioSystem
+ @GuardedBy("mConnectedDevices")
+ private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) {
+ if (state != 0) {
+ return 0;
+ }
+ if ((device & mBecomingNoisyIntentDevices) == 0) {
+ return 0;
+ }
+ int delay = 0;
+ int devices = 0;
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ int dev = mConnectedDevices.valueAt(i).mDeviceType;
+ if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
+ && ((dev & mBecomingNoisyIntentDevices) != 0)) {
+ devices |= dev;
+ }
+ }
+ if (musicDevice == AudioSystem.DEVICE_NONE) {
+ musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ }
+ // ignore condition on device being actually used for music when in communication
+ // because music routing is altered in this case.
+ // also checks whether media routing if affected by a dynamic policy
+ if (((device == musicDevice) || mDeviceBroker.isInCommunication())
+ && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) {
+ mDeviceBroker.broadcastBecomingNoisy();
+ delay = 1000;
+ }
+
+ return delay;
+ }
+
+ // Intent "extra" data keys.
+ private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
+ private static final String CONNECT_INTENT_KEY_STATE = "state";
+ private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
+ private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
+ private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
+ private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
+ private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
+
+ private void sendDeviceConnectionIntent(int device, int state, String address,
+ String deviceName) {
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
+ + " state:0x" + Integer.toHexString(state) + " address:" + address
+ + " name:" + deviceName + ");");
+ }
+ Intent intent = new Intent();
+
+ switch(device) {
+ case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 1);
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+ case AudioSystem.DEVICE_OUT_LINE:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 0);
+ break;
+ case AudioSystem.DEVICE_OUT_USB_HEADSET:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone",
+ AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
+ == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
+ break;
+ case AudioSystem.DEVICE_IN_USB_HEADSET:
+ if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
+ == AudioSystem.DEVICE_STATE_AVAILABLE) {
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 1);
+ } else {
+ // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
+ return;
+ }
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI:
+ case AudioSystem.DEVICE_OUT_HDMI_ARC:
+ configureHdmiPlugIntent(intent, state);
+ break;
+ }
+
+ if (intent.getAction() == null) {
+ return;
+ }
+
+ intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+ intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+ intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void updateAudioRoutes(int device, int state) {
+ int connType = 0;
+
+ switch (device) {
+ case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+ connType = AudioRoutesInfo.MAIN_HEADSET;
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+ case AudioSystem.DEVICE_OUT_LINE:
+ connType = AudioRoutesInfo.MAIN_HEADPHONES;
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI:
+ case AudioSystem.DEVICE_OUT_HDMI_ARC:
+ connType = AudioRoutesInfo.MAIN_HDMI;
+ break;
+ case AudioSystem.DEVICE_OUT_USB_DEVICE:
+ case AudioSystem.DEVICE_OUT_USB_HEADSET:
+ connType = AudioRoutesInfo.MAIN_USB;
+ break;
+ }
+
+ synchronized (mCurAudioRoutes) {
+ if (connType == 0) {
+ return;
+ }
+ int newConn = mCurAudioRoutes.mainType;
+ if (state != 0) {
+ newConn |= connType;
+ } else {
+ newConn &= ~connType;
+ }
+ if (newConn != mCurAudioRoutes.mainType) {
+ mCurAudioRoutes.mainType = newConn;
+ mDeviceBroker.postReportNewRoutes();
+ }
+ }
+ }
+
+ private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
+ intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+ intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
+ if (state != AudioService.CONNECTION_STATE_CONNECTED) {
+ return;
+ }
+ ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+ int[] portGeneration = new int[1];
+ int status = AudioSystem.listAudioPorts(ports, portGeneration);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
+ return;
+ }
+ for (AudioPort port : ports) {
+ if (!(port instanceof AudioDevicePort)) {
+ continue;
+ }
+ final AudioDevicePort devicePort = (AudioDevicePort) port;
+ if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
+ && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
+ continue;
+ }
+ // found an HDMI port: format the list of supported encodings
+ int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
+ if (formats.length > 0) {
+ ArrayList<Integer> encodingList = new ArrayList(1);
+ for (int format : formats) {
+ // a format in the list can be 0, skip it
+ if (format != AudioFormat.ENCODING_INVALID) {
+ encodingList.add(format);
+ }
+ }
+ final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
+ intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
+ }
+ // find the maximum supported number of channels
+ int maxChannels = 0;
+ for (int mask : devicePort.channelMasks()) {
+ int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
+ if (channelCount > maxChannels) {
+ maxChannels = channelCount;
+ }
+ }
+ intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index de389bc..df33bf2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -35,14 +36,9 @@
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
-import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -65,14 +61,12 @@
import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager;
import android.media.AudioAttributes;
-import android.media.AudioDevicePort;
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
-import android.media.AudioPort;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
@@ -104,7 +98,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -120,8 +113,6 @@
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
@@ -137,10 +128,8 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.audio.AudioServiceEvents.ForceUseEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
-import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -150,6 +139,8 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
@@ -175,19 +166,20 @@
implements AccessibilityManager.TouchExplorationStateChangeListener,
AccessibilityManager.AccessibilityServicesStateChangeListener {
- private static final String TAG = "AudioService";
+ private static final String TAG = "AS.AudioService";
/** Debug audio mode */
- protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+ protected static final boolean DEBUG_MODE = false;
/** Debug audio policy feature */
- protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+ protected static final boolean DEBUG_AP = false;
/** Debug volumes */
- protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
+ protected static final boolean DEBUG_VOL = false;
/** debug calls to devices APIs */
- protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
+ protected static final boolean DEBUG_DEVICES = false;
+
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -213,11 +205,11 @@
return mPlatformType == AudioSystem.PLATFORM_VOICE;
}
- private boolean isPlatformTelevision() {
+ /*package*/ boolean isPlatformTelevision() {
return mPlatformType == AudioSystem.PLATFORM_TELEVISION;
}
- private boolean isPlatformAutomotive() {
+ /*package*/ boolean isPlatformAutomotive() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
@@ -242,52 +234,40 @@
private static final int MSG_SET_FORCE_USE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_SET_ALL_VOLUMES = 10;
- private static final int MSG_REPORT_NEW_ROUTES = 12;
- private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
- private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
- private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
- private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
- private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
- private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
- private static final int MSG_SYSTEM_READY = 21;
- private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
- private static final int MSG_UNMUTE_STREAM = 24;
- private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
- private static final int MSG_INDICATE_SYSTEM_READY = 26;
- private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
- private static final int MSG_NOTIFY_VOL_EVENT = 28;
- private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29;
- private static final int MSG_ENABLE_SURROUND_FORMATS = 30;
+ private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
+ private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
+ private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
+ private static final int MSG_SYSTEM_READY = 16;
+ private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
+ private static final int MSG_UNMUTE_STREAM = 18;
+ private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
+ private static final int MSG_INDICATE_SYSTEM_READY = 20;
+ private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21;
+ private static final int MSG_NOTIFY_VOL_EVENT = 22;
+ private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23;
+ private static final int MSG_ENABLE_SURROUND_FORMATS = 24;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
- private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
- private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
- private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
- private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
- private static final int MSG_DISABLE_AUDIO_FOR_UID = 104;
- private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105;
- private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106;
- private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107;
+ private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
// end of messages handled under wakelock
- private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
- // Timeout for connection to bluetooth headset service
- private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
// retry delay in case of failure to indicate system ready to AudioFlinger
private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000;
- private static final int BT_HEARING_AID_GAIN_MIN = -128;
-
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
private AudioHandler mAudioHandler;
/** @see VolumeStreamState */
private VolumeStreamState[] mStreamStates;
+
+ /*package*/ VolumeStreamState getStreamState(int stream) {
+ return mStreamStates[stream];
+ }
+
private SettingsObserver mSettingsObserver;
private int mMode = AudioSystem.MODE_NORMAL;
@@ -477,135 +457,13 @@
private final UserRestrictionsListener mUserRestrictionsListener =
new AudioServiceUserRestrictionsListener();
- // Devices currently connected
- // Use makeDeviceListKey() to make a unique key for this list.
- private class DeviceListSpec {
- int mDeviceType;
- String mDeviceName;
- String mDeviceAddress;
- int mDeviceCodecFormat;
-
- DeviceListSpec(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat) {
- mDeviceType = deviceType;
- mDeviceName = deviceName;
- mDeviceAddress = deviceAddress;
- mDeviceCodecFormat = deviceCodecFormat;
- }
-
- public String toString() {
- return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName
- + " address:" + mDeviceAddress
- + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
- }
- }
-
- // Generate a unique key for the mConnectedDevices List by composing the device "type"
- // and the "address" associated with a specific instance of that device type
- private String makeDeviceListKey(int device, String deviceAddress) {
- return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
- }
-
- private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
-
- private class BluetoothA2dpDeviceInfo {
- BluetoothDevice mBtDevice;
- int mVolume;
- int mCodec;
-
- BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) {
- this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
-
- BluetoothA2dpDeviceInfo(BluetoothDevice btDevice,
- int volume, int codec) {
- mBtDevice = btDevice;
- mVolume = volume;
- mCodec = codec;
- }
-
- public BluetoothDevice getBtDevice() {
- return mBtDevice;
- }
-
- public int getVolume() {
- return mVolume;
- }
-
- public int getCodec() {
- return mCodec;
- }
- }
-
- private int mapBluetoothCodecToAudioFormat(int btCodecType) {
- switch (btCodecType) {
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
- return AudioSystem.AUDIO_FORMAT_SBC;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
- return AudioSystem.AUDIO_FORMAT_AAC;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
- return AudioSystem.AUDIO_FORMAT_APTX;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
- return AudioSystem.AUDIO_FORMAT_APTX_HD;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
- return AudioSystem.AUDIO_FORMAT_LDAC;
- default:
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- }
-
- // Forced device usage for communications
- private int mForcedUseForComm;
- private int mForcedUseForCommExt; // External state returned by getters: always consistent
- // with requests by setters
-
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
- private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
-
- // List of clients having issued a SCO start request
- private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
-
- // BluetoothHeadset API to control SCO connection
- private BluetoothHeadset mBluetoothHeadset;
-
- // Bluetooth headset device
- private BluetoothDevice mBluetoothHeadsetDevice;
-
- // Indicate if SCO audio connection is currently active and if the initiator is
- // audio service (internal) or bluetooth headset (external)
- private int mScoAudioState;
- // SCO audio state is not active
- private static final int SCO_STATE_INACTIVE = 0;
- // SCO audio activation request waiting for headset service to connect
- private static final int SCO_STATE_ACTIVATE_REQ = 1;
- // SCO audio state is active or starting due to a request from AudioManager API
- private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
- // SCO audio deactivation request waiting for headset service to connect
- private static final int SCO_STATE_DEACTIVATE_REQ = 4;
- // SCO audio deactivation in progress, waiting for Bluetooth audio intent
- private static final int SCO_STATE_DEACTIVATING = 5;
-
- // SCO audio state is active due to an action in BT handsfree (either voice recognition or
- // in call audio)
- private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
-
- // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
- // originated from an app targeting an API version before JB MR2 and raw audio after that.
- private int mScoAudioMode;
- // SCO audio mode is undefined
- private static final int SCO_MODE_UNDEFINED = -1;
- // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
- private static final int SCO_MODE_VIRTUAL_CALL = 0;
- // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
- private static final int SCO_MODE_RAW = 1;
- // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
- private static final int SCO_MODE_VR = 2;
-
- private static final int SCO_MODE_MAX = 2;
-
- // Current connection state indicated by bluetooth headset
- private int mScoConnectionState;
+ // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+ //TODO candidate to be moved to separate class that handles synchronization
+ @GuardedBy("mDeviceBroker.mSetModeLock")
+ /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+ new ArrayList<SetModeDeathHandler>();
// true if boot sequence has been completed
private boolean mSystemReady;
@@ -636,15 +494,6 @@
// Used to play ringtones outside system_server
private volatile IRingtonePlayer mRingtonePlayer;
- // Request to override default use of A2DP for media.
- private boolean mBluetoothA2dpEnabled;
- private final Object mBluetoothA2dpEnabledLock = new Object();
-
- // Monitoring of audio routes. Protected by mCurAudioRoutes.
- final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
- final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
- = new RemoteCallbackList<IAudioRoutesObserver>();
-
// Devices for which the volume is fixed and VolumePanel slider should be disabled
int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
@@ -669,17 +518,6 @@
private final MediaFocusControl mMediaFocusControl;
- // Reference to BluetoothA2dp to query for volume.
- private BluetoothHearingAid mHearingAid;
- // lock always taken synchronized on mConnectedDevices
- private final Object mHearingAidLock = new Object();
- // Reference to BluetoothA2dp to query for AbsoluteVolume.
- private BluetoothA2dp mA2dp;
- // lock always taken synchronized on mConnectedDevices
- private final Object mA2dpAvrcpLock = new Object();
- // If absolute volume is supported in AVRCP device
- private boolean mAvrcpAbsVolSupported = false;
-
// Pre-scale for Bluetooth Absolute Volume
private float[] mPrescaleAbsoluteVolume = new float[] {
0.5f, // Pre-scale for index 1
@@ -687,8 +525,6 @@
0.85f, // Pre-scale for index 3
};
- private static Long mLastDeviceConnectMsgTime = new Long(0);
-
private NotificationManager mNm;
private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
@@ -705,15 +541,6 @@
@GuardedBy("mSettingsLock")
private int mAssistantUid;
- // Intent "extra" data keys.
- public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
- public static final String CONNECT_INTENT_KEY_STATE = "state";
- public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
- public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
- public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
- public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
- public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
-
// Defines the format for the connection "address" for ALSA devices
public static String makeAlsaAddressString(int card, int device) {
return "card=" + card + ";device=" + device + ";";
@@ -858,8 +685,6 @@
sSoundEffectVolumeDb = context.getResources().getInteger(
com.android.internal.R.integer.config_soundEffectVolumeDb);
- mForcedUseForComm = AudioSystem.FORCE_NONE;
-
createAudioSystemThread();
AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -886,6 +711,8 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this);
+
// must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
// array initialized by updateStreamVolumeAlias()
updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
@@ -988,23 +815,7 @@
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
0, 0, null, 0);
- mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
- resetBluetoothSco();
- getBluetoothHeadset();
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- sendStickyBroadcastToAll(newIntent);
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.A2DP);
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEARING_AID);
- }
+ mDeviceBroker.onSystemReady();
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
synchronized (mHdmiClientLock) {
@@ -1065,39 +876,22 @@
readAndSetLowRamDevice();
- // Restore device connection states
- synchronized (mConnectedDevices) {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec spec = mConnectedDevices.valueAt(i);
- AudioSystem.setDeviceConnectionState(
- spec.mDeviceType,
- AudioSystem.DEVICE_STATE_AVAILABLE,
- spec.mDeviceAddress,
- spec.mDeviceName,
- spec.mDeviceCodecFormat);
- }
- }
+ // Restore device connection states, BT state
+ mDeviceBroker.onAudioServerDied();
+
// Restore call state
if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) {
mModeLogger.log(new AudioEventLogger.StringEvent(
"onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
}
- // Restore forced usage for communications and record
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
final int forSys;
synchronized (mSettingsLock) {
forSys = mCameraSoundForced ?
AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
}
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
+
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied");
// Restore stream volumes
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1120,20 +914,10 @@
RotationHelper.updateOrientation();
}
- synchronized (mBluetoothA2dpEnabledLock) {
- final int forMed = mBluetoothA2dpEnabled ?
- AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed);
- }
-
synchronized (mSettingsLock) {
final int forDock = mDockAudioMediaEnabled ?
AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
updateAssistantUId(true);
@@ -1209,6 +993,45 @@
}
}
+ /**
+ * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected.
+ */
+ /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+ if (state != 0) {
+ // DEVICE_OUT_HDMI is now connected
+ if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ }
+
+ if (isPlatformTelevision()) {
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+ checkAllFixedVolumeDevices();
+ synchronized (mHdmiClientLock) {
+ if (mHdmiManager != null && mHdmiPlaybackClient != null) {
+ mHdmiCecSink = false;
+ mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+ }
+ }
+ }
+ sendEnabledSurroundFormats(mContentResolver, true);
+ } else {
+ // DEVICE_OUT_HDMI disconnected
+ if (isPlatformTelevision()) {
+ synchronized (mHdmiClientLock) {
+ if (mHdmiManager != null) {
+ mHdmiCecSink = false;
+ }
+ }
+ }
+ }
+ }
+
private void checkAllFixedVolumeDevices()
{
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1373,7 +1196,7 @@
private void sendEncodedSurroundMode(ContentResolver cr, String eventSource)
{
- int encodedSurroundMode = Settings.Global.getInt(
+ final int encodedSurroundMode = Settings.Global.getInt(
cr, Settings.Global.ENCODED_SURROUND_OUTPUT,
Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
sendEncodedSurroundMode(encodedSurroundMode, eventSource);
@@ -1402,13 +1225,8 @@
break;
}
if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) {
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_ENCODED_SURROUND,
- forceSetting,
- eventSource,
- 0);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting,
+ eventSource);
}
}
@@ -1632,7 +1450,7 @@
+ ", flags=" + flags + ", caller=" + caller
+ ", volControlStream=" + mVolumeControlStream
+ ", userSelect=" + mUserSelectedVolumeControlStream);
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
.append("/").append(caller).append(" uid:").append(uid).toString()));
final int streamType;
@@ -1690,7 +1508,7 @@
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
return;
}
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid());
@@ -1871,16 +1689,18 @@
if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
- }
+ if (DEBUG_VOL) {
+ Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+ + newIndex + "stream=" + streamType);
}
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex);
}
// Check if volume update should be send to Hearing Aid
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
- setHearingAidVolume(newIndex, streamType);
+ Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex
+ + " stream=" + streamType);
+ mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
}
// Check if volume update should be sent to Hdmi system audio.
@@ -2052,7 +1872,7 @@
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage));
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
@@ -2127,18 +1947,20 @@
index = rescaleIndex(index * 10, streamType, streamTypeAlias);
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.setAvrcpAbsoluteVolume(index / 10);
- }
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+ + "stream=" + streamType);
}
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
}
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
- setHearingAidVolume(index, streamType);
+ Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+ + " stream=" + streamType);
+ mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
}
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
@@ -2881,6 +2703,10 @@
}
}
+ /*package*/ void setUpdateRingerModeServiceInt() {
+ setRingerModeInt(getRingerModeInternal(), false);
+ }
+
/** @see AudioManager#shouldVibrate(int) */
public boolean shouldVibrate(int vibrateType) {
if (!mHasVibrator) return false;
@@ -2921,7 +2747,7 @@
}
- private class SetModeDeathHandler implements IBinder.DeathRecipient {
+ /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
private int mPid;
private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
@@ -2934,7 +2760,7 @@
public void binderDied() {
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
+ synchronized (mDeviceBroker.mSetModeLock) {
Log.w(TAG, "setMode() client died");
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -2949,9 +2775,7 @@
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- final long ident = Binder.clearCallingIdentity();
- disconnectBluetoothSco(newModeOwnerPid);
- Binder.restoreCallingIdentity(ident);
+ mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
}
}
@@ -2994,7 +2818,7 @@
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
+ synchronized (mDeviceBroker.mSetModeLock) {
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
@@ -3006,11 +2830,11 @@
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- disconnectBluetoothSco(newModeOwnerPid);
+ mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
}
}
- // must be called synchronized on mSetModeDeathHandlers
+ // must be called synchronized on mSetModeLock
// setModeInt() returns a valid PID if the audio mode was successfully set to
// any mode other than NORMAL.
private int setModeInt(int mode, IBinder cb, int pid, String caller) {
@@ -3380,26 +3204,12 @@
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
-
- if (on) {
- if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE,
- eventSource, 0);
- }
- mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
- } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
-
- mForcedUseForCommExt = mForcedUseForComm;
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
+ mDeviceBroker.setSpeakerphoneOn(on, eventSource);
}
/** @see AudioManager#isSpeakerphoneOn() */
public boolean isSpeakerphoneOn() {
- return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+ return mDeviceBroker.isSpeakerphoneOn();
}
/** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -3410,7 +3220,7 @@
// Only enable calls from system components
if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
- mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+ mDeviceBroker.setBluetoothScoOnByApp(on);
return;
}
@@ -3418,95 +3228,57 @@
final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
- setBluetoothScoOnInt(on, eventSource);
+
+ mDeviceBroker.setBluetoothScoOn(on, eventSource);
}
- public void setBluetoothScoOnInt(boolean on, String eventSource) {
- Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
- if (on) {
- // do not accept SCO ON if SCO audio is not connected
- synchronized (mScoClients) {
- if ((mBluetoothHeadset != null)
- && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
- mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
- Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
- + mBluetoothHeadsetDevice + " is not in audio connected mode");
- return;
- }
- }
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
- mForcedUseForCommExt = mForcedUseForComm;
- AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0);
- // Un-mute ringtone stream volume
- setRingerModeInt(getRingerModeInternal(), false);
- }
-
- /** @see AudioManager#isBluetoothScoOn() */
+ /** @see AudioManager#isBluetoothScoOn()
+ * Note that it doesn't report internal state, but state seen by apps (which may have
+ * called setBluetoothScoOn() */
public boolean isBluetoothScoOn() {
- return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
+ return mDeviceBroker.isBluetoothScoOnForApp();
}
+ // TODO investigate internal users due to deprecation of SDK API
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
// for logging only
final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
-
- synchronized (mBluetoothA2dpEnabledLock) {
- if (mBluetoothA2dpEnabled == on) {
- return;
- }
- mBluetoothA2dpEnabled = on;
- sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- eventSource, 0);
- }
+ mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource);
}
/** @see AudioManager#isBluetoothA2dpOn() */
public boolean isBluetoothA2dpOn() {
- synchronized (mBluetoothA2dpEnabledLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mDeviceBroker.isBluetoothA2dpOn();
}
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
- int scoAudioMode =
+ final int scoAudioMode =
(targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
- SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
- startBluetoothScoInt(cb, scoAudioMode);
+ BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
+ final String eventSource = new StringBuilder("startBluetoothSco()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ startBluetoothScoInt(cb, scoAudioMode, eventSource);
}
/** @see AudioManager#startBluetoothScoVirtualCall() */
public void startBluetoothScoVirtualCall(IBinder cb) {
- startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+ final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
}
- void startBluetoothScoInt(IBinder cb, int scoAudioMode){
+ void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
if (!checkAudioSettingsPermission("startBluetoothSco()") ||
!mSystemReady) {
return;
}
- ScoClient client = getScoClient(cb, true);
- // The calling identity must be cleared before calling ScoClient.incCount().
- // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- // The caller identity must be cleared after getScoClient() because it is needed if a new
- // client is created.
- final long ident = Binder.clearCallingIdentity();
- client.incCount(scoAudioMode);
- Binder.restoreCallingIdentity(ident);
+ mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
}
/** @see AudioManager#stopBluetoothSco() */
@@ -3515,648 +3287,15 @@
!mSystemReady) {
return;
}
- ScoClient client = getScoClient(cb, false);
- // The calling identity must be cleared before calling ScoClient.decCount().
- // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- final long ident = Binder.clearCallingIdentity();
- if (client != null) {
- client.decCount();
- }
- Binder.restoreCallingIdentity(ident);
+ final String eventSource = new StringBuilder("stopBluetoothSco()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
}
- private class ScoClient implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- private int mCreatorPid;
- private int mStartcount; // number of SCO connections started by this client
-
- ScoClient(IBinder cb) {
- mCb = cb;
- mCreatorPid = Binder.getCallingPid();
- mStartcount = 0;
- }
-
- public void binderDied() {
- synchronized(mScoClients) {
- Log.w(TAG, "SCO client died");
- int index = mScoClients.indexOf(this);
- if (index < 0) {
- Log.w(TAG, "unregistered SCO client died");
- } else {
- clearCount(true);
- mScoClients.remove(this);
- }
- }
- }
-
- public void incCount(int scoAudioMode) {
- synchronized(mScoClients) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
- if (mStartcount == 0) {
- try {
- mCb.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // client has already died!
- Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death");
- }
- }
- mStartcount++;
- }
- }
-
- public void decCount() {
- synchronized(mScoClients) {
- if (mStartcount == 0) {
- Log.w(TAG, "ScoClient.decCount() already 0");
- } else {
- mStartcount--;
- if (mStartcount == 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "decCount() going to 0 but not registered to binder");
- }
- }
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public void clearCount(boolean stopSco) {
- synchronized(mScoClients) {
- if (mStartcount != 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
- }
- }
- mStartcount = 0;
- if (stopSco) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public int getCount() {
- return mStartcount;
- }
-
- public IBinder getBinder() {
- return mCb;
- }
-
- public int getPid() {
- return mCreatorPid;
- }
-
- public int totalCount() {
- synchronized(mScoClients) {
- int count = 0;
- for (ScoClient mScoClient : mScoClients) {
- count += mScoClient.getCount();
- }
- return count;
- }
- }
-
- private void requestScoState(int state, int scoAudioMode) {
- checkScoAudioState();
- int clientCount = totalCount();
- if (clientCount != 0) {
- Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
- + ", clientCount=" + clientCount);
- return;
- }
- if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- // Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client process.
- synchronized(mSetModeDeathHandlers) {
- int modeOwnerPid = mSetModeDeathHandlers.isEmpty()
- ? 0 : mSetModeDeathHandlers.get(0).getPid();
- if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
- Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
- + modeOwnerPid + " != creatorPid " + mCreatorPid);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return;
- }
- switch (mScoAudioState) {
- case SCO_STATE_INACTIVE:
- mScoAudioMode = scoAudioMode;
- if (scoAudioMode == SCO_MODE_UNDEFINED) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- if (mBluetoothHeadsetDevice != null) {
- mScoAudioMode = Settings.Global.getInt(mContentResolver,
- "bluetooth_sco_channel_"
- + mBluetoothHeadsetDevice.getAddress(),
- SCO_MODE_VIRTUAL_CALL);
- if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- }
- }
- }
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " connection, mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- }
- if (mBluetoothHeadsetDevice == null) {
- Log.w(TAG, "requestScoState: no active device while connecting,"
- + " mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- } else {
- Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
- + " failed, mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- case SCO_STATE_DEACTIVATING:
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
- break;
- default:
- Log.w(TAG, "requestScoState: failed to connect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
-
- }
- }
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVE_INTERNAL:
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " disconnection, mScoAudioMode=" + mScoAudioMode);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- }
- if (mBluetoothHeadsetDevice == null) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_DEACTIVATING;
- } else {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- case SCO_STATE_ACTIVATE_REQ:
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- default:
- Log.w(TAG, "requestScoState: failed to disconnect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- }
- }
- }
-
- private void checkScoAudioState() {
- synchronized (mScoClients) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState == SCO_STATE_INACTIVE &&
- mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- }
- }
-
-
- private ScoClient getScoClient(IBinder cb, boolean create) {
- synchronized(mScoClients) {
- for (ScoClient existingClient : mScoClients) {
- if (existingClient.getBinder() == cb) {
- return existingClient;
- }
- }
- if (create) {
- ScoClient newClient = new ScoClient(cb);
- mScoClients.add(newClient);
- return newClient;
- }
- return null;
- }
- }
-
- public void clearAllScoClients(int exceptPid, boolean stopSco) {
- synchronized(mScoClients) {
- ScoClient savedClient = null;
- for (ScoClient cl : mScoClients) {
- if (cl.getPid() != exceptPid) {
- cl.clearCount(stopSco);
- } else {
- savedClient = cl;
- }
- }
- mScoClients.clear();
- if (savedClient != null) {
- mScoClients.add(savedClient);
- }
- }
- }
-
- private boolean getBluetoothHeadset() {
- boolean result = false;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
- // If we could not get a bluetooth headset proxy, send a failure message
- // without delay to reset the SCO audio state and clear SCO clients.
- // If we could get a proxy, send a delayed failure message that will reset our state
- // in case we don't receive onServiceConnected().
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
- return result;
- }
-
- /**
- * Disconnect all SCO connections started by {@link AudioManager} except those started by
- * {@param exceptPid}
- *
- * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
- */
- private void disconnectBluetoothSco(int exceptPid) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- return;
- }
- clearAllScoClients(exceptPid, true);
- }
- }
-
- private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
- BluetoothDevice device, int scoAudioMode) {
- switch (scoAudioMode) {
- case SCO_MODE_RAW:
- return bluetoothHeadset.disconnectAudio();
- case SCO_MODE_VIRTUAL_CALL:
- return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
- case SCO_MODE_VR:
- return bluetoothHeadset.stopVoiceRecognition(device);
- default:
- return false;
- }
- }
-
- private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
- BluetoothDevice device, int scoAudioMode) {
- switch (scoAudioMode) {
- case SCO_MODE_RAW:
- return bluetoothHeadset.connectAudio();
- case SCO_MODE_VIRTUAL_CALL:
- return bluetoothHeadset.startScoUsingVirtualVoiceCall();
- case SCO_MODE_VR:
- return bluetoothHeadset.startVoiceRecognition(device);
- default:
- return false;
- }
- }
-
- private void resetBluetoothSco() {
- synchronized(mScoClients) {
- clearAllScoClients(0, false);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- AudioSystem.setParameters("A2dpSuspended=false");
- setBluetoothScoOnInt(false, "resetBluetoothSco");
- }
-
- private void broadcastScoConnectionState(int state) {
- sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
- SENDMSG_QUEUE, state, 0, null, 0);
- }
-
- private void onBroadcastScoConnectionState(int state) {
- if (state != mScoConnectionState) {
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
- mScoConnectionState);
- sendStickyBroadcastToAll(newIntent);
- mScoConnectionState = state;
- }
- }
-
- private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
- if (btDevice == null) {
- return true;
- }
- String address = btDevice.getAddress();
- BluetoothClass btClass = btDevice.getBluetoothClass();
- int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
- int[] outDeviceTypes = {
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
- };
- if (btClass != null) {
- switch (btClass.getDeviceClass()) {
- case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
- case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
- outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
- break;
- case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
- outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
- break;
- }
- }
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- String btDeviceName = btDevice.getName();
- boolean result = false;
- if (isActive) {
- result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName);
- } else {
- for (int outDeviceType : outDeviceTypes) {
- result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName);
- }
- }
- // handleDeviceConnection() && result to make sure the method get executed
- result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
- return result;
- }
-
- private void setBtScoActiveDevice(BluetoothDevice btDevice) {
- synchronized (mScoClients) {
- Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
- final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
- if (!Objects.equals(btDevice, previousActiveDevice)) {
- if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
- Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
- + previousActiveDevice);
- }
- if (!handleBtScoActiveDeviceChange(btDevice, true)) {
- Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
- // set mBluetoothHeadsetDevice to null when failing to add new device
- btDevice = null;
- }
- mBluetoothHeadsetDevice = btDevice;
- if (mBluetoothHeadsetDevice == null) {
- resetBluetoothSco();
- }
- }
- }
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- BluetoothDevice btDevice;
- List<BluetoothDevice> deviceList;
- switch(profile) {
- case BluetoothProfile.A2DP:
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- mA2dp = (BluetoothA2dp) proxy;
- deviceList = mA2dp.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- int state = mA2dp.getConnectionState(btDevice);
- int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
- int delay = checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
- AudioSystem.DEVICE_NONE);
- final String addr = btDevice == null ? "null" : btDevice.getAddress();
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2DP service connected: device addr=" + addr
- + " state=" + state));
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SINK_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(btDevice),
- delay);
- }
- }
- }
- break;
-
- case BluetoothProfile.A2DP_SINK:
- deviceList = proxy.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- synchronized (mConnectedDevices) {
- int state = proxy.getConnectionState(btDevice);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SRC_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(btDevice),
- 0 /* delay */);
- }
- }
- break;
-
- case BluetoothProfile.HEADSET:
- synchronized (mScoClients) {
- // Discard timeout message
- mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
- // Refresh SCO audio state
- checkScoAudioState();
- // Continue pending action if any
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
- boolean status = false;
- if (mBluetoothHeadsetDevice != null) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVATE_REQ:
- status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode);
- if (status) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- }
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode);
- if (status) {
- mScoAudioState = SCO_STATE_DEACTIVATING;
- }
- break;
- }
- }
- if (!status) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- }
- }
- break;
-
- case BluetoothProfile.HEARING_AID:
- synchronized (mConnectedDevices) {
- synchronized (mHearingAidLock) {
- mHearingAid = (BluetoothHearingAid) proxy;
- deviceList = mHearingAid.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- int state = mHearingAid.getConnectionState(btDevice);
- int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
- int delay = checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_HEARING_AID, intState,
- AudioSystem.DEVICE_NONE);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_HEARING_AID_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- btDevice,
- delay);
- }
- }
- }
-
- break;
-
- default:
- break;
- }
- }
- public void onServiceDisconnected(int profile) {
-
- switch (profile) {
- case BluetoothProfile.A2DP:
- disconnectA2dp();
- break;
-
- case BluetoothProfile.A2DP_SINK:
- disconnectA2dpSink();
- break;
-
- case BluetoothProfile.HEADSET:
- disconnectHeadset();
- break;
-
- case BluetoothProfile.HEARING_AID:
- disconnectHearingAid();
- break;
-
- default:
- break;
- }
- }
- };
-
- void disconnectAllBluetoothProfiles() {
- disconnectA2dp();
- disconnectA2dpSink();
- disconnectHeadset();
- disconnectHearingAid();
- }
-
- void disconnectA2dp() {
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- 0, AudioSystem.DEVICE_NONE);
- for (int i = 0; i < toRemove.size(); i++) {
- makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay);
- }
- }
- }
- }
- }
-
- void disconnectA2dpSink() {
- synchronized (mConnectedDevices) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
- for(int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- for (int i = 0; i < toRemove.size(); i++) {
- makeA2dpSrcUnavailable(toRemove.valueAt(i));
- }
- }
- }
- }
-
- void disconnectHeadset() {
- synchronized (mScoClients) {
- setBtScoActiveDevice(null);
- mBluetoothHeadset = null;
- }
- }
-
- void disconnectHearingAid() {
- synchronized (mConnectedDevices) {
- synchronized (mHearingAidLock) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_OUT_HEARING_AID devices
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
- 0, AudioSystem.DEVICE_NONE);
- for (int i = 0; i < toRemove.size(); i++) {
- makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/);
- }
- }
- }
- }
+ /*package*/ ContentResolver getContentResolver() {
+ return mContentResolver;
}
private void onCheckMusicActive(String caller) {
@@ -4173,8 +3312,8 @@
caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
- (index > safeMediaVolumeIndex(device))) {
+ if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
+ && (index > safeMediaVolumeIndex(device))) {
// Approximate cumulative active music time
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
@@ -4192,8 +3331,7 @@
mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
}
- private int getSafeUsbMediaVolumeIndex()
- {
+ private int getSafeUsbMediaVolumeIndex() {
// determine UI volume index corresponding to the wanted safe gain in dBFS
int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -4201,7 +3339,7 @@
mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
- while (Math.abs(max-min) > 1) {
+ while (Math.abs(max - min) > 1) {
int index = (max + min) / 2;
float gainDB = AudioSystem.getStreamVolumeDB(
AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
@@ -4518,7 +3656,7 @@
|| adjust == AudioManager.ADJUST_TOGGLE_MUTE;
}
- private boolean isInCommunication() {
+ /*package*/ boolean isInCommunication() {
boolean IsInCall = false;
TelecomManager telecomManager =
@@ -4671,25 +3809,9 @@
} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
return;
}
- synchronized (mLastDeviceConnectMsgTime) {
- long time = SystemClock.uptimeMillis() + delay;
- if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
- msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
- msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
- msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
- msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
- msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE ||
- msg == MSG_BTA2DP_DOCK_TIMEOUT) {
- if (mLastDeviceConnectMsgTime >= time) {
- // add a little delay to make sure messages are ordered as expected
- time = mLastDeviceConnectMsgTime + 30;
- }
- mLastDeviceConnectMsgTime = time;
- }
-
- handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
- }
+ final long time = SystemClock.uptimeMillis() + delay;
+ handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
}
boolean checkAudioSettingsPermission(String method) {
@@ -4704,7 +3826,7 @@
return false;
}
- private int getDeviceForStream(int stream) {
+ /*package*/ int getDeviceForStream(int stream) {
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
// Multiple device selection is either:
@@ -4749,160 +3871,94 @@
}
}
- private int getA2dpCodec(BluetoothDevice device) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
- if (btCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
- if (btCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
- }
+
+ /*package*/ void observeDevicesForAllStreams() {
+ observeDevicesForStreams(-1);
}
- /*
- * A class just for packaging up a set of connection parameters.
+ /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
+ /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
+ /**
+ * The states that can be used with AudioService.setWiredDeviceConnectionState()
+ * Attention: those values differ from those in BluetoothProfile, follow annotations to
+ * distinguish between @ConnectionState and @BtProfileConnectionState
*/
- class WiredDeviceConnectionState {
- public final int mType;
- public final int mState;
- public final String mAddress;
- public final String mName;
- public final String mCaller;
+ @IntDef({
+ CONNECTION_STATE_DISCONNECTED,
+ CONNECTION_STATE_CONNECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConnectionState {}
- public WiredDeviceConnectionState(int type, int state, String address, String name,
- String caller) {
- mType = type;
- mState = state;
- mAddress = address;
- mName = name;
- mCaller = caller;
- }
- }
-
- public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ /**
+ * see AudioManager.setWiredDeviceConnectionState()
+ */
+ public void setWiredDeviceConnectionState(int type,
+ @ConnectionState int state, String address, String name,
String caller) {
- synchronized (mConnectedDevices) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
- + address + ")");
- }
- int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
- 0 /* arg1 unused */,
- 0 /* arg2 unused */,
- new WiredDeviceConnectionState(type, state, address, name, caller),
- delay);
+ if (state != CONNECTION_STATE_CONNECTED
+ && state != CONNECTION_STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Invalid state " + state);
}
+ mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
}
+ /**
+ * @hide
+ * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
+ * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+ */
+ @IntDef({
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtProfileConnectionState {}
+
public int setBluetoothHearingAidDeviceConnectionState(
- BluetoothDevice device, int state, boolean suppressNoisyIntent,
- int musicDevice)
+ @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice)
{
- int delay;
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "setHearingAidDeviceConnectionState state=" + state
- + " addr=" + device.getAddress()
- + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG));
- synchronized (mConnectedDevices) {
- if (!suppressNoisyIntent) {
- int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
- delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
- intState, musicDevice);
- } else {
- delay = 0;
- }
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_HEARING_AID_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- device,
- delay);
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
}
- return delay;
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ + " (dis)connection, got " + state);
+ }
+ return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+ device, state, suppressNoisyIntent, musicDevice, "AudioService");
}
- public int setBluetoothA2dpDeviceConnectionState(
- BluetoothDevice device, int state, int profile)
- {
- return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- device, state, profile, false /* suppressNoisyIntent */,
- -1 /* a2dpVolume */);
+ /**
+ * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+ */
+ public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
+ }
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ + " (dis)connection, got " + state);
+ }
+ return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
+ profile, suppressNoisyIntent, a2dpVolume);
}
- public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device,
- int state, int profile, boolean suppressNoisyIntent, int a2dpVolume)
- {
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
- // only querying address as this is the only readily available field on the device
- + " addr=" + device.getAddress()
- + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
- + " vol=" + a2dpVolume)).printLog(TAG));
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored"));
- return 0;
- }
- return setBluetoothA2dpDeviceConnectionStateInt(
- device, state, profile, suppressNoisyIntent,
- AudioSystem.DEVICE_NONE, a2dpVolume);
- }
-
- public int setBluetoothA2dpDeviceConnectionStateInt(
- BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
- int musicDevice, int a2dpVolume)
- {
- int delay;
- if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
- throw new IllegalArgumentException("invalid profile " + profile);
- }
- synchronized (mConnectedDevices) {
- if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
- int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
- delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- intState, musicDevice);
- } else {
- delay = 0;
- }
-
- int a2dpCodec = getA2dpCodec(device);
-
- if (DEBUG_DEVICES) {
- Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device
- + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
- + " suppressNoisyIntent: " + suppressNoisyIntent);
- }
-
- queueMsgUnderWakeLock(mAudioHandler,
- (profile == BluetoothProfile.A2DP ?
- MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
- state,
- 0, /* arg2 unused */
- new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec),
- delay);
- }
- return delay;
- }
-
+ /**
+ * See AudioManager.handleBluetoothA2dpDeviceConfigChange()
+ * @param device
+ */
public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
{
- synchronized (mConnectedDevices) {
- int a2dpCodec = getA2dpCodec(device);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_A2DP_DEVICE_CONFIG_CHANGE,
- 0 /* arg1 unused */,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec),
- 0 /* delay */);
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
}
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
}
/**
@@ -4912,52 +3968,18 @@
public int handleBluetoothA2dpActiveDeviceChange(
BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
int a2dpVolume) {
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
+ }
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
throw new IllegalArgumentException("invalid profile " + profile);
}
-
- synchronized (mConnectedDevices) {
- if (state == BluetoothA2dp.STATE_CONNECTED) {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- continue;
- }
- // If A2DP device exists, this is either an active device change or
- // device config change
- String existingDevicekey = mConnectedDevices.keyAt(i);
- String deviceName = device.getName();
- String address = device.getAddress();
- String newDeviceKey = makeDeviceListKey(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
- int a2dpCodec = getA2dpCodec(device);
- // Device not equal to existing device, active device change
- if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
- mConnectedDevices.remove(existingDevicekey);
- mConnectedDevices.put(newDeviceKey, new DeviceListSpec(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
- address, a2dpCodec));
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_A2DP_ACTIVE_DEVICE_CHANGE,
- 0,
- 0,
- new BluetoothA2dpDeviceInfo(
- device, a2dpVolume, a2dpCodec),
- 0 /* delay */);
- return 0;
- } else {
- // Device config change for existing device
- handleBluetoothA2dpDeviceConfigChange(device);
- return 0;
- }
- }
- }
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Invalid state " + state);
}
-
- // New device connection or a device disconnect
- return setBluetoothA2dpDeviceConnectionStateInt(
- device, state, profile, suppressNoisyIntent,
- AudioSystem.DEVICE_NONE, a2dpVolume);
+ return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+ suppressNoisyIntent, a2dpVolume);
}
private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG =
@@ -4967,24 +3989,27 @@
AudioSystem.DEVICE_OUT_ALL_USB |
AudioSystem.DEVICE_OUT_HDMI;
+ /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
+ sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
+ newDevice, 0, null, 0);
+ }
+
private void onAccessoryPlugMediaUnmute(int newDevice) {
if (DEBUG_VOL) {
Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]",
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
- synchronized (mConnectedDevices) {
- if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
- && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
- && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
- && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
- && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
- {
- if (DEBUG_VOL) {
- Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
- newDevice, AudioSystem.getOutputDeviceName(newDevice)));
- }
- mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
+
+ if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+ && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
+ && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+ && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+ if (DEBUG_VOL) {
+ Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+ newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
+ mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
}
}
@@ -4994,7 +4019,7 @@
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
- // 2 mSetModeDeathHandlers
+ // 2 mSetModeLock
// 3 mSettingsLock
// 4 VolumeStreamState.class
public class VolumeStreamState {
@@ -5138,11 +4163,11 @@
}
// must be called while synchronized VolumeStreamState.class
- public void applyDeviceVolume_syncVSS(int device) {
+ /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
int index;
if (mIsMuted) {
index = 0;
- } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+ } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
@@ -5151,10 +4176,11 @@
} else {
index = (getIndex(device) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
public void applyAllVolumes() {
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// apply device specific volumes first
int index;
@@ -5164,7 +4190,7 @@
if (mIsMuted) {
index = 0;
} else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- mAvrcpAbsVolSupported) {
+ isAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
@@ -5173,7 +4199,7 @@
} else {
index = (mIndexMap.valueAt(i) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
}
// apply default volume last: by convention , default device volume will be used
@@ -5183,7 +4209,7 @@
} else {
index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(
+ AudioSystem.setStreamVolumeIndexAS(
mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
}
}
@@ -5373,6 +4399,7 @@
}
public void checkFixedVolumeDevices() {
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// ignore settings for fixed volume devices: volume should always be at max or 0
if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5383,7 +4410,7 @@
|| (((device & mFixedVolumeDevices) != 0) && index != 0)) {
mIndexMap.put(device, mIndexMax);
}
- applyDeviceVolume_syncVSS(device);
+ applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
}
}
}
@@ -5465,11 +4492,13 @@
}
}
- private void setDeviceVolume(VolumeStreamState streamState, int device) {
+ /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
+
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// Apply volume
- streamState.applyDeviceVolume_syncVSS(device);
+ streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -5479,11 +4508,13 @@
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
- if ((device != streamDevice) && mAvrcpAbsVolSupported &&
- ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
- mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+ if ((device != streamDevice) && isAvrcpAbsVolSupported
+ && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
+ isAvrcpAbsVolSupported);
}
- mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
+ isAvrcpAbsVolSupported);
}
}
}
@@ -5762,12 +4793,6 @@
}
}
- private void setForceUse(int usage, int config, String eventSource) {
- synchronized (mConnectedDevices) {
- setForceUseInt_SyncDevices(usage, config, eventSource);
- }
- }
-
private void onPersistSafeVolumeState(int state) {
Settings.Global.putInt(mContentResolver,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5834,56 +4859,20 @@
onPlaySoundEffect(msg.arg1, msg.arg2);
break;
- case MSG_BTA2DP_DOCK_TIMEOUT:
- // msg.obj == address of BTA2DP device
- synchronized (mConnectedDevices) {
- makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
- }
- mAudioEventWakeLock.release();
- break;
-
case MSG_SET_FORCE_USE:
- case MSG_SET_FORCE_BT_A2DP_USE:
- setForceUse(msg.arg1, msg.arg2, (String) msg.obj);
- break;
-
- case MSG_BT_HEADSET_CNCT_FAILED:
- resetBluetoothSco();
- break;
-
- case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
- { WiredDeviceConnectionState connectState =
- (WiredDeviceConnectionState)msg.obj;
- mDeviceLogger.log(new WiredDevConnectEvent(connectState));
- onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
- connectState.mAddress, connectState.mName, connectState.mCaller);
- mAudioEventWakeLock.release();
+ {
+ final String eventSource = (String) msg.obj;
+ final int useCase = msg.arg1;
+ final int config = msg.arg2;
+ if (useCase == AudioSystem.FOR_MEDIA) {
+ Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from "
+ + eventSource);
+ break;
}
- break;
-
- case MSG_SET_A2DP_SRC_CONNECTION_STATE:
- onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_SET_A2DP_SINK_CONNECTION_STATE:
- onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_SET_HEARING_AID_CONNECTION_STATE:
- onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_A2DP_DEVICE_CONFIG_CHANGE:
- onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_A2DP_ACTIVE_DEVICE_CHANGE:
- onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj);
- mAudioEventWakeLock.release();
+ sForceUseLogger.log(
+ new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+ AudioSystem.setForceUse(useCase, config);
+ }
break;
case MSG_DISABLE_AUDIO_FOR_UID:
@@ -5892,35 +4881,10 @@
mAudioEventWakeLock.release();
break;
- case MSG_REPORT_NEW_ROUTES: {
- int N = mRoutesObservers.beginBroadcast();
- if (N > 0) {
- AudioRoutesInfo routes;
- synchronized (mCurAudioRoutes) {
- routes = new AudioRoutesInfo(mCurAudioRoutes);
- }
- while (N > 0) {
- N--;
- IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
- try {
- obs.dispatchAudioRoutesChanged(routes);
- } catch (RemoteException e) {
- }
- }
- }
- mRoutesObservers.finishBroadcast();
- observeDevicesForStreams(-1);
- break;
- }
-
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
- case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
- onSendBecomingNoisyIntent();
- break;
-
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
@@ -5930,10 +4894,6 @@
onPersistSafeVolumeState(msg.arg1);
break;
- case MSG_BROADCAST_BT_CONNECTION_STATE:
- onBroadcastScoConnectionState(msg.arg1);
- break;
-
case MSG_SYSTEM_READY:
onSystemReady();
break;
@@ -6033,20 +4993,7 @@
if (mEncodedSurroundMode != newSurroundMode) {
// Send to AudioPolicyManager
sendEncodedSurroundMode(newSurroundMode, "SettingsObserver");
- synchronized(mConnectedDevices) {
- // Is HDMI connected?
- String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
- DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec != null) {
- // Toggle HDMI to retrigger broadcast with proper formats.
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
- "android"); // disconnect
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
- "android"); // reconnect
- }
- }
+ mDeviceBroker.toggleHdmiIfConnected_Async();
mEncodedSurroundMode = newSurroundMode;
mSurroundModeChanged = true;
} else {
@@ -6055,515 +5002,18 @@
}
}
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceAvailable(
- String address, String name, String eventSource, int a2dpCodec) {
- // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
- // audio policy manager
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- setBluetoothA2dpOnInt(true, eventSource);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
- // Reset A2DP suspend state each time a new sink is connected
- AudioSystem.setParameters("A2dpSuspended=false");
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
- new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0);
- setCurrentAudioRouteNameIfPossible(name);
- }
-
- private void onSendBecomingNoisyIntent() {
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
- sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
- if (address == null) {
- return;
- }
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = false;
- }
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
- // Remove A2DP routes as well
- setCurrentAudioRouteNameIfPossible(null);
- if (mDockAddress == address) {
- mDockAddress = null;
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
- // prevent any activity on the A2DP audio output to avoid unwanted
- // reconnection of the sink.
- AudioSystem.setParameters("A2dpSuspended=true");
- // Retrieve deviceSpec before removing device
- String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
- DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
- int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat :
- AudioSystem.AUDIO_FORMAT_DEFAULT;
- // the device will be made unavailable later, so consider it disconnected right away
- mConnectedDevices.remove(deviceKey);
- // send the delayed message to make the device unavailable later
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_BTA2DP_DOCK_TIMEOUT,
- a2dpCodec,
- 0,
- address,
- delayMs);
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcAvailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
- new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcUnavailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
- }
-
- private void setHearingAidVolume(int index, int streamType) {
- synchronized (mHearingAidLock) {
- if (mHearingAid != null) {
- //hearing aid expect volume value in range -128dB to 0dB
- int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10,
- AudioSystem.DEVICE_OUT_HEARING_AID);
- if (gainDB < BT_HEARING_AID_GAIN_MIN)
- gainDB = BT_HEARING_AID_GAIN_MIN;
- mHearingAid.setVolume(gainDB);
- }
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
- int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
- setHearingAidVolume(index, AudioSystem.STREAM_MUSIC);
-
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
- new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name,
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0);
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_HEARING_AID, 0,
- mStreamStates[AudioSystem.STREAM_MUSIC], 0);
- setCurrentAudioRouteNameIfPossible(name);
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeHearingAidDeviceUnavailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
- // Remove Hearing Aid routes as well
- setCurrentAudioRouteNameIfPossible(null);
- }
-
- // must be called synchronized on mConnectedDevices
- private void cancelA2dpDeviceTimeout() {
- mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- // must be called synchronized on mConnectedDevices
- private boolean hasScheduledA2dpDockTimeout() {
- return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
- {
- if (btInfo == null) {
- return;
- }
-
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpVolume = btInfo.getVolume();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state
- + " is dock: " + btDevice.isBluetoothDock());
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- btDevice.getAddress());
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // introduction of a delay for transient disconnections of docks when
- // power is rapidly turned off/on, this message will be canceled if
- // we reconnect the dock under a preset delay
- makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS);
- // the next time isConnected is evaluated, it will be false for the dock
- }
- } else {
- makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat);
- }
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- // this could be a reconnection after a transient disconnection
- cancelA2dpDeviceTimeout();
- mDockAddress = address;
- } else {
- // this could be a connection of another A2DP device before the timeout of
- // a dock: cancel the dock timeout, and make the dock unavailable now
- if (hasScheduledA2dpDockTimeout() && mDockAddress != null) {
- cancelA2dpDeviceTimeout();
- makeA2dpDeviceUnavailableNow(mDockAddress,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
- }
- if (a2dpVolume != -1) {
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- // Convert index to internal representation in VolumeStreamState
- a2dpVolume = a2dpVolume * 10;
- streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- "onSetA2dpSinkConnectionState");
- setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
- }
- makeA2dpDeviceAvailable(address, btDevice.getName(),
- "onSetA2dpSinkConnectionState", a2dpCodec);
- }
- }
- }
-
- private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
- {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
-
- if (DEBUG_VOL) {
- Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcUnavailable(address);
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcAvailable(address);
- }
- }
- }
-
- private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state)
- {
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
- btDevice.getAddress());
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- makeHearingAidDeviceUnavailable(address);
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- makeHearingAidDeviceAvailable(address, btDevice.getName(),
- "onSetHearingAidConnectionState");
- }
- }
- }
-
- private void setCurrentAudioRouteNameIfPossible(String name) {
- synchronized (mCurAudioRoutes) {
- if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
- if (name != null || !isCurrentDeviceConnected()) {
- mCurAudioRoutes.bluetoothName = name;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
- }
-
- private boolean isCurrentDeviceConnected() {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) {
- return true;
- }
- }
- return false;
- }
-
- private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo)
- {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "onBluetoothA2dpDeviceConfigChange addr=" + address));
-
- int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mConnectedDevices) {
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2dp config change ignored"));
- return;
- }
- final String key = makeDeviceListKey(device, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec == null) {
- return;
- }
- // Device is connected
- if (deviceSpec.mDeviceCodecFormat != a2dpCodec) {
- deviceSpec.mDeviceCodecFormat = a2dpCodec;
- mConnectedDevices.replace(key, deviceSpec);
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
- + deviceSpec.mDeviceCodecFormat);
- }
- if (AudioSystem.handleDeviceConfigChange(device, address,
- btDevice.getName(), deviceSpec.mDeviceCodecFormat)
- != AudioSystem.AUDIO_STATUS_OK) {
- int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- // force A2DP device disconnection in case of error so that AudioService state is
- // consistent with audio policy manager state
- setBluetoothA2dpDeviceConnectionStateInt(
- btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
- false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
- }
- }
- }
-
- /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */
- public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpVolume = btInfo.getVolume();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "onBluetoothA2dpActiveDeviceChange addr=" + address));
-
- int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mConnectedDevices) {
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2dp config change ignored"));
- return;
- }
- final String key = makeDeviceListKey(device, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec == null) {
- return;
- }
-
- // Device is connected
- if (a2dpVolume != -1) {
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- // Convert index to internal representation in VolumeStreamState
- a2dpVolume = a2dpVolume * 10;
- streamState.setIndex(a2dpVolume, device,
- "onBluetoothA2dpActiveDeviceChange");
- setDeviceVolume(streamState, device);
- }
-
- if (AudioSystem.handleDeviceConfigChange(device, address,
- btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
- int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- // force A2DP device disconnection in case of error so that AudioService state is
- // consistent with audio policy manager state
- setBluetoothA2dpDeviceConnectionStateInt(
- btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
- false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
- }
- }
- }
-
public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = support;
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+ mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
+ sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
mStreamStates[AudioSystem.STREAM_MUSIC], 0);
- }
- }
-
- private boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device)
- + " address:" + address + " name:" + deviceName + ")");
- }
- synchronized (mConnectedDevices) {
- String deviceKey = makeDeviceListKey(device, address);
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "deviceKey:" + deviceKey);
- }
- DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
- boolean isConnected = deviceSpec != null;
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected);
- }
- if (connect && !isConnected) {
- final int res = AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- if (res != AudioSystem.AUDIO_STATUS_OK) {
- Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) +
- " due to command error " + res );
- return false;
- }
- mConnectedDevices.put(deviceKey, new DeviceListSpec(device,
- deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- device, 0, null, 0);
- return true;
- } else if (!connect && isConnected) {
- AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- // always remove even if disconnection failed
- mConnectedDevices.remove(deviceKey);
- return true;
- }
- Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
- + deviceSpec + ", connect=" + connect);
- }
- return false;
- }
-
- // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
- // sent if:
- // - none of these devices are connected anymore after one is disconnected AND
- // - the device being disconnected is actually used for music.
- // Access synchronized on mConnectedDevices
- int mBecomingNoisyIntentDevices =
- AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
- AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE |
- AudioSystem.DEVICE_OUT_HEARING_AID;
-
- // must be called before removing the device from mConnectedDevices
- // Called synchronized on mConnectedDevices
- // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
- // from AudioSystem
- private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
- int delay = 0;
- if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
- int devices = 0;
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- int dev = mConnectedDevices.valueAt(i).mDeviceType;
- if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
- && ((dev & mBecomingNoisyIntentDevices) != 0)) {
- devices |= dev;
- }
- }
- if (musicDevice == AudioSystem.DEVICE_NONE) {
- musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- }
- // ignore condition on device being actually used for music when in communication
- // because music routing is altered in this case.
- // also checks whether media routing if affected by a dynamic policy
- if (((device == musicDevice) || isInCommunication()) && (device == devices)
- && !hasMediaDynamicPolicy()) {
- mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY);
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
- delay = 1000;
- }
- }
-
- return delay;
}
/**
* @return true if there is currently a registered dynamic mixing policy that affects media
*/
- private boolean hasMediaDynamicPolicy() {
+ /*package*/ boolean hasMediaDynamicPolicy() {
synchronized (mAudioPolicies) {
if (mAudioPolicies.isEmpty()) {
return false;
@@ -6578,213 +5028,25 @@
}
}
- private void updateAudioRoutes(int device, int state)
- {
- int connType = 0;
-
- if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
- connType = AudioRoutesInfo.MAIN_HEADSET;
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
- device == AudioSystem.DEVICE_OUT_LINE) {
- connType = AudioRoutesInfo.MAIN_HEADPHONES;
- } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
- device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
- connType = AudioRoutesInfo.MAIN_HDMI;
- } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
- device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- connType = AudioRoutesInfo.MAIN_USB;
- }
-
- synchronized (mCurAudioRoutes) {
- if (connType != 0) {
- int newConn = mCurAudioRoutes.mainType;
- if (state != 0) {
- newConn |= connType;
- } else {
- newConn &= ~connType;
- }
- if (newConn != mCurAudioRoutes.mainType) {
- mCurAudioRoutes.mainType = newConn;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
+ /*package*/ void checkMusicActive(int deviceType, String caller) {
+ if ((deviceType & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
}
}
- private void sendDeviceConnectionIntent(int device, int state, String address,
- String deviceName) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
- " state:0x" + Integer.toHexString(state) + " address:" + address +
- " name:" + deviceName + ");");
- }
- Intent intent = new Intent();
-
- if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 1);
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
- device == AudioSystem.DEVICE_OUT_LINE) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 0);
- } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone",
- AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
- == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
- } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
- if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
- == AudioSystem.DEVICE_STATE_AVAILABLE) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 1);
- } else {
- // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
- return;
- }
- } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
- device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
- configureHdmiPlugIntent(intent, state);
- }
-
- if (intent.getAction() == null) {
- return;
- }
-
- intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
- intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
- intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
- AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_LINE |
- AudioSystem.DEVICE_OUT_ALL_USB;
-
- private void onSetWiredDeviceConnectionState(int device, int state, String address,
- String deviceName, String caller) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
- + " state:" + Integer.toHexString(state)
- + " address:" + address
- + " deviceName:" + deviceName
- + " caller: " + caller + ");");
- }
-
- synchronized (mConnectedDevices) {
- if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
- setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
- }
-
- if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
- // change of connection state failed, bailout
- return;
- }
- if (state != 0) {
- if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
- setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
- }
- if ((device & mSafeMediaVolumeDevices) != 0) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
- }
- // Television devices without CEC service apply software volume on HDMI output
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
- checkAllFixedVolumeDevices();
- synchronized (mHdmiClientLock) {
- if (mHdmiManager != null && mHdmiPlaybackClient != null) {
- mHdmiCecSink = false;
- mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
- }
- }
- }
- if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
- sendEnabledSurroundFormats(mContentResolver, true);
- }
- } else {
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- synchronized (mHdmiClientLock) {
- if (mHdmiManager != null) {
- mHdmiCecSink = false;
- }
- }
- }
- }
- sendDeviceConnectionIntent(device, state, address, deviceName);
- updateAudioRoutes(device, state);
- }
- }
-
- private void configureHdmiPlugIntent(Intent intent, int state) {
- intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
- intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
- if (state == 1) {
- ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
- int[] portGeneration = new int[1];
- int status = AudioSystem.listAudioPorts(ports, portGeneration);
- if (status == AudioManager.SUCCESS) {
- for (AudioPort port : ports) {
- if (port instanceof AudioDevicePort) {
- final AudioDevicePort devicePort = (AudioDevicePort) port;
- if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
- devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
- // format the list of supported encodings
- int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
- if (formats.length > 0) {
- ArrayList<Integer> encodingList = new ArrayList(1);
- for (int format : formats) {
- // a format in the list can be 0, skip it
- if (format != AudioFormat.ENCODING_INVALID) {
- encodingList.add(format);
- }
- }
- int[] encodingArray = new int[encodingList.size()];
- for (int i = 0 ; i < encodingArray.length ; i++) {
- encodingArray[i] = encodingList.get(i);
- }
- intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
- }
- // find the maximum supported number of channels
- int maxChannels = 0;
- for (int mask : devicePort.channelMasks()) {
- int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
- if (channelCount > maxChannels) {
- maxChannels = channelCount;
- }
- }
- intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
- }
- }
- }
- }
- }
- }
-
- /* cache of the address of the last dock the device was connected to */
- private String mDockAddress;
-
/**
* Receiver for misc intent broadcasts the Phone app cares about.
*/
private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
int outDevice;
int inDevice;
int state;
@@ -6812,75 +5074,16 @@
}
// Low end docks have a menu to enable or disable audio
// (see mDockAudioMediaEnabled)
- if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
- ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) &&
- (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config,
- "ACTION_DOCK_EVENT intent"));
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
+ if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK)
+ || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config,
+ "ACTION_DOCK_EVENT intent");
}
mDockState = dockState;
- } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
- BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- setBtScoActiveDevice(btDevice);
- } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
- boolean broadcast = false;
- int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
- synchronized (mScoClients) {
- int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- // broadcast intent if the connection was initated by AudioService
- if (!mScoClients.isEmpty() &&
- (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
- mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATING)) {
- broadcast = true;
- }
- switch (btState) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- setBluetoothScoOn(true);
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- setBluetoothScoOn(false);
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- // startBluetoothSco called after stopBluetoothSco
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
- && connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcast = false;
- break;
- }
- }
- // Tear down SCO if disconnected from external
- clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
- mScoAudioState = SCO_STATE_INACTIVE;
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- default:
- // do not broadcast CONNECTING or invalid state
- broadcast = false;
- break;
- }
- }
- if (broadcast) {
- broadcastScoConnectionState(scoAudioState);
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
- sendStickyBroadcastToAll(newIntent);
- }
+ } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
+ || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ mDeviceBroker.receiveBtEvent(intent);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
if (mMonitorRotation) {
RotationHelper.enable();
@@ -6898,13 +5101,7 @@
if (mUserSwitchedReceived) {
// attempt to stop music playback for background user except on first user
// switch (i.e. first boot)
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
+ mDeviceBroker.broadcastBecomingNoisy();
}
mUserSwitchedReceived = true;
// the current audio focus owner is no longer valid
@@ -6938,7 +5135,7 @@
state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_OFF ||
state == BluetoothAdapter.STATE_TURNING_OFF) {
- disconnectAllBluetoothProfiles();
+ mDeviceBroker.disconnectAllBluetoothProfiles();
}
} else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
@@ -7178,22 +5375,17 @@
// take new state into account for streams muted by ringer mode
setRingerModeInt(getRingerModeInternal(), false);
}
-
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_SYSTEM,
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
cameraSoundForced ?
AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- new String("handleConfigurationChanged"),
- 0);
-
+ "handleConfigurationChanged");
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
0,
0,
mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+
}
}
mVolumeController.setLayoutDirection(config.getLayoutDirection());
@@ -7202,28 +5394,6 @@
}
}
- // Handles request to override default use of A2DP for media.
- // Must be called synchronized on mConnectedDevices
- public void setBluetoothA2dpOnInt(boolean on, String eventSource) {
- synchronized (mBluetoothA2dpEnabledLock) {
- mBluetoothA2dpEnabled = on;
- mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
- setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- eventSource);
- }
- }
-
- // Must be called synchronized on mConnectedDevices
- private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) {
- if (usage == AudioSystem.FOR_MEDIA) {
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource));
- AudioSystem.setForceUse(usage, config);
- }
-
@Override
public void setRingtonePlayer(IRingtonePlayer player) {
mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
@@ -7237,11 +5407,7 @@
@Override
public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
- synchronized (mCurAudioRoutes) {
- AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
- mRoutesObservers.register(observer);
- return routes;
- }
+ return mDeviceBroker.startWatchingRoutes(observer);
}
@@ -7283,9 +5449,9 @@
// the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
private int mSafeUsbMediaVolumeIndex;
// mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
- private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_USB_HEADSET;
+ /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET
+ | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_USB_HEADSET;
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
@@ -7438,9 +5604,8 @@
mHdmiSystemAudioSupported = on;
final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
AudioSystem.FORCE_NONE;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
- config, "setHdmiSystemAudioSupported"));
- AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
+ "setHdmiSystemAudioSupported");
}
device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
}
@@ -7553,14 +5718,14 @@
// logs for wired + A2DP device connections:
// - wired: logged before onSetWiredDeviceConnectionState() is executed
// - A2DP: logged at reception of method call
- final private AudioEventLogger mDeviceLogger = new AudioEventLogger(
- LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection");
+ /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger(
+ LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI");
- final private AudioEventLogger mForceUseLogger = new AudioEventLogger(
+ static final AudioEventLogger sForceUseLogger = new AudioEventLogger(
LOG_NB_EVENTS_FORCE_USE,
"force use (logged before setForceUse() is executed)");
- final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
+ static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
"volume changes (logged when command received by AudioService)");
final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY,
@@ -7613,8 +5778,9 @@
dumpStreamStates(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
- pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType));
- pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName);
+ pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
+ mDeviceBroker.getCurAudioRoutes().mainType));
+ pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName);
pw.println("\nOther state:");
pw.print(" mVolumeController="); pw.println(mVolumeController);
@@ -7630,7 +5796,8 @@
pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced);
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
- pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
+ pw.print(" mAvrcpAbsVolSupported=");
+ pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
dumpAudioPolicies(pw);
mDynPolicyLogger.dump(pw);
@@ -7643,11 +5810,11 @@
pw.println("\nEvent logs:");
mModeLogger.dump(pw);
pw.println("\n");
- mDeviceLogger.dump(pw);
+ sDeviceLogger.dump(pw);
pw.println("\n");
- mForceUseLogger.dump(pw);
+ sForceUseLogger.dump(pw);
pw.println("\n");
- mVolumeLogger.dump(pw);
+ sVolumeLogger.dump(pw);
}
private static String safeMediaVolumeStateToString(int state) {
@@ -8270,6 +6437,11 @@
}
//======================
+ // Audio device management
+ //======================
+ private final AudioDeviceBroker mDeviceBroker;
+
+ //======================
// Audio policy proxy
//======================
private static final class AudioDeviceArray {
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 9d9e35b..7ccb45e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -19,7 +19,7 @@
import android.media.AudioManager;
import android.media.AudioSystem;
-import com.android.server.audio.AudioService.WiredDeviceConnectionState;
+import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState;
public class AudioServiceEvents {
@@ -89,9 +89,11 @@
}
final static class VolumeEvent extends AudioEventLogger.Event {
- final static int VOL_ADJUST_SUGG_VOL = 0;
- final static int VOL_ADJUST_STREAM_VOL = 1;
- final static int VOL_SET_STREAM_VOL = 2;
+ static final int VOL_ADJUST_SUGG_VOL = 0;
+ static final int VOL_ADJUST_STREAM_VOL = 1;
+ static final int VOL_SET_STREAM_VOL = 2;
+ static final int VOL_SET_HEARING_AID_VOL = 3;
+ static final int VOL_SET_AVRCP_VOL = 4;
final int mOp;
final int mStream;
@@ -107,6 +109,24 @@
mCaller = caller;
}
+ VolumeEvent(int op, int index, int gainDb) {
+ mOp = op;
+ mVal1 = index;
+ mVal2 = gainDb;
+ //unused
+ mStream = -1;
+ mCaller = null;
+ }
+
+ VolumeEvent(int op, int index) {
+ mOp = op;
+ mVal1 = index;
+ //unused
+ mVal2 = 0;
+ mStream = -1;
+ mCaller = null;
+ }
+
@Override
public String eventToString() {
switch (mOp) {
@@ -131,6 +151,15 @@
.append(" flags:0x").append(Integer.toHexString(mVal2))
.append(") from ").append(mCaller)
.toString();
+ case VOL_SET_HEARING_AID_VOL:
+ return new StringBuilder("setHearingAidVolume:")
+ .append(" index:").append(mVal1)
+ .append(" gain dB:").append(mVal2)
+ .toString();
+ case VOL_SET_AVRCP_VOL:
+ return new StringBuilder("setAvrcpVolume:")
+ .append(" index:").append(mVal1)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
new file mode 100644
index 0000000..bf32501
--- /dev/null
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -0,0 +1,949 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * @hide
+ * Class to encapsulate all communication with Bluetooth services
+ */
+public class BtHelper {
+
+ private static final String TAG = "AS.BtHelper";
+
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+ BtHelper(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
+ // List of clients having issued a SCO start request
+ private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
+
+ // BluetoothHeadset API to control SCO connection
+ private BluetoothHeadset mBluetoothHeadset;
+
+ // Bluetooth headset device
+ private BluetoothDevice mBluetoothHeadsetDevice;
+
+ // Indicate if SCO audio connection is currently active and if the initiator is
+ // audio service (internal) or bluetooth headset (external)
+ private int mScoAudioState;
+ // SCO audio state is not active
+ private static final int SCO_STATE_INACTIVE = 0;
+ // SCO audio activation request waiting for headset service to connect
+ private static final int SCO_STATE_ACTIVATE_REQ = 1;
+ // SCO audio state is active or starting due to a request from AudioManager API
+ private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+ // SCO audio deactivation request waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_REQ = 4;
+ // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+ private static final int SCO_STATE_DEACTIVATING = 5;
+
+ // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+ // in call audio)
+ private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+
+ // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+ // originated from an app targeting an API version before JB MR2 and raw audio after that.
+ private int mScoAudioMode;
+ // SCO audio mode is undefined
+ /*package*/ static final int SCO_MODE_UNDEFINED = -1;
+ // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+ /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0;
+ // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+ private static final int SCO_MODE_RAW = 1;
+ // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+ private static final int SCO_MODE_VR = 2;
+
+ private static final int SCO_MODE_MAX = 2;
+
+ // Current connection state indicated by bluetooth headset
+ private int mScoConnectionState;
+
+ private static final int BT_HEARING_AID_GAIN_MIN = -128;
+
+ @GuardedBy("mDeviceBroker.mHearingAidLock")
+ private BluetoothHearingAid mHearingAid;
+
+ // Reference to BluetoothA2dp to query for AbsoluteVolume.
+ @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+ private BluetoothA2dp mA2dp;
+ // If absolute volume is supported in AVRCP device
+ @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+ private boolean mAvrcpAbsVolSupported = false;
+
+ //----------------------------------------------------------------------
+ /*package*/ static class BluetoothA2dpDeviceInfo {
+ private final @NonNull BluetoothDevice mBtDevice;
+ private final int mVolume;
+ private final int mCodec;
+
+ BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
+ this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+
+ BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
+ mBtDevice = btDevice;
+ mVolume = volume;
+ mCodec = codec;
+ }
+
+ public @NonNull BluetoothDevice getBtDevice() {
+ return mBtDevice;
+ }
+
+ public int getVolume() {
+ return mVolume;
+ }
+
+ public int getCodec() {
+ return mCodec;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Interface for AudioDeviceBroker
+
+ /*package*/ void onSystemReady() {
+ mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
+ resetBluetoothSco();
+ getBluetoothHeadset();
+
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ sendStickyBroadcastToAll(newIntent);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
+ adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+ }
+ }
+
+ @GuardedBy("mBluetoothA2dpEnabledLock")
+ /*package*/ void onAudioServerDiedRestoreA2dp() {
+ final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
+ ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+ return (mA2dp != null && mAvrcpAbsVolSupported);
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+ mAvrcpAbsVolSupported = supported;
+ }
+
+ /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) {
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ if (mA2dp == null) {
+ if (AudioService.DEBUG_VOL) {
+ Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
+ return;
+ }
+ }
+ if (!mAvrcpAbsVolSupported) {
+ return;
+ }
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
+ }
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
+ mA2dp.setAvrcpAbsoluteVolume(index / 10);
+ }
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+ if (mA2dp == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ if (btCodecStatus == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+ if (btCodecConfig == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+ }
+
+ /*package*/ void receiveBtEvent(Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
+ BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ setBtScoActiveDevice(btDevice);
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ boolean broadcast = false;
+ int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ synchronized (mScoClients) {
+ int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ // broadcast intent if the connection was initated by AudioService
+ if (!mScoClients.isEmpty()
+ && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
+ || mScoAudioState == SCO_STATE_ACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATING)) {
+ broadcast = true;
+ }
+ switch (btState) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ // startBluetoothSco called after stopBluetoothSco
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+ && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcast = false;
+ break;
+ }
+ }
+ // Tear down SCO if disconnected from external
+ clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ break;
+ default:
+ // do not broadcast CONNECTING or invalid state
+ broadcast = false;
+ break;
+ }
+ }
+ if (broadcast) {
+ broadcastScoConnectionState(scoAudioState);
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+ sendStickyBroadcastToAll(newIntent);
+ }
+ }
+ }
+
+ /**
+ *
+ * @return false if SCO isn't connected
+ */
+ /*package*/ boolean isBluetoothScoOn() {
+ synchronized (mScoClients) {
+ if ((mBluetoothHeadset != null)
+ && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+ Log.w(TAG, "isBluetoothScoOn(true) returning false because "
+ + mBluetoothHeadsetDevice + " is not in audio connected mode");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Disconnect all SCO connections started by {@link AudioManager} except those started by
+ * {@param exceptPid}
+ *
+ * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
+ */
+ /*package*/ void disconnectBluetoothSco(int exceptPid) {
+ synchronized (mScoClients) {
+ checkScoAudioState();
+ if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
+ return;
+ }
+ clearAllScoClients(exceptPid, true);
+ }
+ }
+
+ /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+ @NonNull String eventSource) {
+ ScoClient client = getScoClient(cb, true);
+ // The calling identity must be cleared before calling ScoClient.incCount().
+ // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ // The caller identity must be cleared after getScoClient() because it is needed if a new
+ // client is created.
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ eventSource += " client count before=" + client.getCount();
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+ client.incCount(scoAudioMode);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Null ScoClient", e);
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) {
+ ScoClient client = getScoClient(cb, false);
+ // The calling identity must be cleared before calling ScoClient.decCount().
+ // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ final long ident = Binder.clearCallingIdentity();
+ if (client != null) {
+ eventSource += " client count before=" + client.getCount();
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+ client.decCount();
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+
+
+ /*package*/ void setHearingAidVolume(int index, int streamType) {
+ synchronized (mDeviceBroker.mHearingAidLock) {
+ if (mHearingAid == null) {
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setHearingAidVolume: null mHearingAid");
+ }
+ return;
+ }
+ //hearing aid expect volume value in range -128dB to 0dB
+ int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
+ AudioSystem.DEVICE_OUT_HEARING_AID);
+ if (gainDB < BT_HEARING_AID_GAIN_MIN) {
+ gainDB = BT_HEARING_AID_GAIN_MIN;
+ }
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+ + index + " gain=" + gainDB);
+ }
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ mHearingAid.setVolume(gainDB);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ private void broadcastScoConnectionState(int state) {
+ mDeviceBroker.broadcastScoConnectionState(state);
+ }
+
+ /*package*/ void onBroadcastScoConnectionState(int state) {
+ if (state == mScoConnectionState) {
+ return;
+ }
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+ mScoConnectionState);
+ sendStickyBroadcastToAll(newIntent);
+ mScoConnectionState = state;
+ }
+
+ private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+ if (btDevice == null) {
+ return true;
+ }
+ String address = btDevice.getAddress();
+ BluetoothClass btClass = btDevice.getBluetoothClass();
+ int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ int[] outDeviceTypes = {
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
+ };
+ if (btClass != null) {
+ switch (btClass.getDeviceClass()) {
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
+ break;
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
+ break;
+ }
+ }
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ String btDeviceName = btDevice.getName();
+ boolean result = false;
+ if (isActive) {
+ result |= mDeviceBroker.handleDeviceConnection(
+ isActive, outDeviceTypes[0], address, btDeviceName);
+ } else {
+ for (int outDeviceType : outDeviceTypes) {
+ result |= mDeviceBroker.handleDeviceConnection(
+ isActive, outDeviceType, address, btDeviceName);
+ }
+ }
+ // handleDeviceConnection() && result to make sure the method get executed
+ result = mDeviceBroker.handleDeviceConnection(
+ isActive, inDevice, address, btDeviceName) && result;
+ return result;
+ }
+
+ private void setBtScoActiveDevice(BluetoothDevice btDevice) {
+ synchronized (mScoClients) {
+ Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
+ final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+ if (Objects.equals(btDevice, previousActiveDevice)) {
+ return;
+ }
+ if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+ Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+ + previousActiveDevice);
+ }
+ if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+ Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+ // set mBluetoothHeadsetDevice to null when failing to add new device
+ btDevice = null;
+ }
+ mBluetoothHeadsetDevice = btDevice;
+ if (mBluetoothHeadsetDevice == null) {
+ resetBluetoothSco();
+ }
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ final BluetoothDevice btDevice;
+ List<BluetoothDevice> deviceList;
+ switch(profile) {
+ case BluetoothProfile.A2DP:
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ mA2dp = (BluetoothA2dp) proxy;
+ deviceList = mA2dp.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ if (btDevice == null) {
+ Log.e(TAG, "Invalid null device in BT profile listener");
+ return;
+ }
+ final @BluetoothProfile.BtProfileState int state =
+ mA2dp.getConnectionState(btDevice);
+ mDeviceBroker.handleSetA2dpSinkConnectionState(
+ state, new BluetoothA2dpDeviceInfo(btDevice));
+ }
+ }
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ deviceList = proxy.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ final @BluetoothProfile.BtProfileState int state =
+ proxy.getConnectionState(btDevice);
+ mDeviceBroker.handleSetA2dpSourceConnectionState(
+ state, new BluetoothA2dpDeviceInfo(btDevice));
+ }
+ break;
+
+ case BluetoothProfile.HEADSET:
+ synchronized (mScoClients) {
+ // Discard timeout message
+ mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
+ // Refresh SCO audio state
+ checkScoAudioState();
+ // Continue pending action if any
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
+ boolean status = false;
+ if (mBluetoothHeadsetDevice != null) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVATE_REQ:
+ status = connectBluetoothScoAudioHelper(
+ mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ status = disconnectBluetoothScoAudioHelper(
+ mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ }
+ break;
+ }
+ }
+ if (!status) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+ }
+ break;
+
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = (BluetoothHearingAid) proxy;
+ deviceList = mHearingAid.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ final @BluetoothProfile.BtProfileState int state =
+ mHearingAid.getConnectionState(btDevice);
+ mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+ btDevice, state,
+ /*suppressNoisyIntent*/ false,
+ /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
+ /*eventSource*/ "mBluetoothProfileServiceListener");
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ public void onServiceDisconnected(int profile) {
+
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mDeviceBroker.handleDisconnectA2dp();
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ mDeviceBroker.handleDisconnectA2dpSink();
+ break;
+
+ case BluetoothProfile.HEADSET:
+ disconnectHeadset();
+ break;
+
+ case BluetoothProfile.HEARING_AID:
+ mDeviceBroker.handleDisconnectHearingAid();
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+
+ void disconnectAllBluetoothProfiles() {
+ mDeviceBroker.handleDisconnectA2dp();
+ mDeviceBroker.handleDisconnectA2dpSink();
+ disconnectHeadset();
+ mDeviceBroker.handleDisconnectHearingAid();
+ }
+
+ private void disconnectHeadset() {
+ synchronized (mScoClients) {
+ setBtScoActiveDevice(null);
+ mBluetoothHeadset = null;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ private class ScoClient implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+ private int mCreatorPid;
+ private int mStartcount; // number of SCO connections started by this client
+
+ ScoClient(IBinder cb) {
+ mCb = cb;
+ mCreatorPid = Binder.getCallingPid();
+ mStartcount = 0;
+ }
+
+ public void binderDied() {
+ synchronized (mScoClients) {
+ Log.w(TAG, "SCO client died");
+ int index = mScoClients.indexOf(this);
+ if (index < 0) {
+ Log.w(TAG, "unregistered SCO client died");
+ } else {
+ clearCount(true);
+ mScoClients.remove(this);
+ }
+ }
+ }
+
+ public void incCount(int scoAudioMode) {
+ synchronized (mScoClients) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+ if (mStartcount == 0) {
+ try {
+ mCb.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // client has already died!
+ Log.w(TAG, "ScoClient incCount() could not link to "
+ + mCb + " binder death");
+ }
+ }
+ mStartcount++;
+ }
+ }
+
+ public void decCount() {
+ synchronized (mScoClients) {
+ if (mStartcount == 0) {
+ Log.w(TAG, "ScoClient.decCount() already 0");
+ } else {
+ mStartcount--;
+ if (mStartcount == 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "decCount() going to 0 but not registered to binder");
+ }
+ }
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public void clearCount(boolean stopSco) {
+ synchronized (mScoClients) {
+ if (mStartcount != 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "clearCount() mStartcount: "
+ + mStartcount + " != 0 but not registered to binder");
+ }
+ }
+ mStartcount = 0;
+ if (stopSco) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public int getCount() {
+ return mStartcount;
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+
+ public int getPid() {
+ return mCreatorPid;
+ }
+
+ public int totalCount() {
+ synchronized (mScoClients) {
+ int count = 0;
+ for (ScoClient mScoClient : mScoClients) {
+ count += mScoClient.getCount();
+ }
+ return count;
+ }
+ }
+
+ private void requestScoState(int state, int scoAudioMode) {
+ checkScoAudioState();
+ int clientCount = totalCount();
+ if (clientCount != 0) {
+ Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+ + ", clientCount=" + clientCount);
+ return;
+ }
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ // Make sure that the state transitions to CONNECTING even if we cannot initiate
+ // the connection.
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+ // currently controlled by the same client process.
+ synchronized (mDeviceBroker.mSetModeLock) {
+ int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty()
+ ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid();
+ if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+ Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+ + modeOwnerPid + " != creatorPid " + mCreatorPid);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return;
+ }
+ switch (mScoAudioState) {
+ case SCO_STATE_INACTIVE:
+ mScoAudioMode = scoAudioMode;
+ if (scoAudioMode == SCO_MODE_UNDEFINED) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ if (mBluetoothHeadsetDevice != null) {
+ mScoAudioMode = Settings.Global.getInt(
+ mDeviceBroker.getContentResolver(),
+ "bluetooth_sco_channel_"
+ + mBluetoothHeadsetDevice.getAddress(),
+ SCO_MODE_VIRTUAL_CALL);
+ if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ }
+ }
+ }
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " connection, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ Log.w(TAG, "requestScoState: no active device while connecting,"
+ + " mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+ + " failed, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_DEACTIVATING:
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to connect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+
+ }
+ }
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVE_INTERNAL:
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " disconnection, mScoAudioMode=" + mScoAudioMode);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to disconnect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------
+ // Utilities
+ private void sendStickyBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.disconnectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.stopVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
+ private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.connectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.startVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
+ /*package*/ void resetBluetoothSco() {
+ synchronized (mScoClients) {
+ clearAllScoClients(0, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
+ }
+
+
+ private void checkScoAudioState() {
+ synchronized (mScoClients) {
+ if (mBluetoothHeadset != null
+ && mBluetoothHeadsetDevice != null
+ && mScoAudioState == SCO_STATE_INACTIVE
+ && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ }
+ }
+
+
+ private ScoClient getScoClient(IBinder cb, boolean create) {
+ synchronized (mScoClients) {
+ for (ScoClient existingClient : mScoClients) {
+ if (existingClient.getBinder() == cb) {
+ return existingClient;
+ }
+ }
+ if (create) {
+ ScoClient newClient = new ScoClient(cb);
+ mScoClients.add(newClient);
+ return newClient;
+ }
+ return null;
+ }
+ }
+
+ private void clearAllScoClients(int exceptPid, boolean stopSco) {
+ synchronized (mScoClients) {
+ ScoClient savedClient = null;
+ for (ScoClient cl : mScoClients) {
+ if (cl.getPid() != exceptPid) {
+ cl.clearCount(stopSco);
+ } else {
+ savedClient = cl;
+ }
+ }
+ mScoClients.clear();
+ if (savedClient != null) {
+ mScoClients.add(savedClient);
+ }
+ }
+ }
+
+ private boolean getBluetoothHeadset() {
+ boolean result = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ result = adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+ }
+ // If we could not get a bluetooth headset proxy, send a failure message
+ // without delay to reset the SCO audio state and clear SCO clients.
+ // If we could get a proxy, send a delayed failure message that will reset our state
+ // in case we don't receive onServiceConnected().
+ mDeviceBroker.handleFailureToConnectToBtHeadsetService(
+ result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+ return result;
+ }
+
+ private int mapBluetoothCodecToAudioFormat(int btCodecType) {
+ switch (btCodecType) {
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+ return AudioSystem.AUDIO_FORMAT_SBC;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+ return AudioSystem.AUDIO_FORMAT_AAC;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+ return AudioSystem.AUDIO_FORMAT_APTX;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+ return AudioSystem.AUDIO_FORMAT_APTX_HD;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+ return AudioSystem.AUDIO_FORMAT_LDAC;
+ default:
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index dc564ba..3a25d98 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -202,7 +202,7 @@
if (mPrivilegedAlarmActiveCount++ == 0) {
mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
- AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
}
} else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
@@ -211,7 +211,7 @@
if (AudioSystem.getStreamVolumeIndex(
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
mMaxAlarmVolume) {
- AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 41fedc5..15d66e6 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -29,10 +29,12 @@
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
+import android.app.KeyguardManager;
import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -377,6 +379,13 @@
new BiometricTaskStackListener();
private final Random mRandom = new Random();
+ // TODO(b/123378871): Remove when moved.
+ // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app)
+ // receiver. BiometricService internally launches CDCA which invokes BiometricService to
+ // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl
+ // method to poke BiometricService - the result will then be forwarded to this receiver.
+ private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
private AuthSession mCurrentAuthSession;
@@ -705,6 +714,22 @@
}
}
+ // Launch CDC instead if necessary. CDC will return results through an AIDL call, since
+ // we can't get activity results. Store the receiver somewhere so we can forward the
+ // result back to the client.
+ // TODO(b/123378871): Remove when moved.
+ if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) {
+ mConfirmDeviceCredentialReceiver = receiver;
+ final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
+ // Use this so we don't need to duplicate logic..
+ final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
+ null /* description */);
+ // Then give it the bundle to do magic behavior..
+ intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
+ getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+ return;
+ }
+
mHandler.post(() -> {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
final int modality = result.first;
@@ -745,6 +770,36 @@
});
}
+ @Override // Binder call
+ public void onConfirmDeviceCredentialSuccess() {
+ checkInternalPermission();
+ if (mConfirmDeviceCredentialReceiver == null) {
+ Slog.w(TAG, "onCDCASuccess null!");
+ return;
+ }
+ try {
+ mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ mConfirmDeviceCredentialReceiver = null;
+ }
+
+ @Override // Binder call
+ public void onConfirmDeviceCredentialError(int error, String message) {
+ checkInternalPermission();
+ if (mConfirmDeviceCredentialReceiver == null) {
+ Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+ return;
+ }
+ try {
+ mConfirmDeviceCredentialReceiver.onError(error, message);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ mConfirmDeviceCredentialReceiver = null;
+ }
+
/**
* authenticate() (above) which is called from BiometricPrompt determines which
* modality/modalities to start authenticating with. authenticateInternal() should only be
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb3f91b..b89768a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,6 +36,7 @@
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.ColorSpace;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
@@ -93,9 +94,9 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
-import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -294,6 +295,7 @@
// is rejected by the system.
private final Curve mMinimumBrightnessCurve;
private final Spline mMinimumBrightnessSpline;
+ private final ColorSpace mWideColorSpace;
public DisplayManagerService(Context context) {
this(context, new Injector());
@@ -322,6 +324,8 @@
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
mCurrentUserId = UserHandle.USER_SYSTEM;
+ ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
+ mWideColorSpace = colorSpaces[1];
}
public void setupSchedulerPolicies() {
@@ -1073,6 +1077,10 @@
return mMinimumBrightnessCurve;
}
+ int getPreferredWideGamutColorSpaceIdInternal() {
+ return mWideColorSpace.getId();
+ }
+
private void setBrightnessConfigurationForUserInternal(
@Nullable BrightnessConfiguration c, @UserIdInt int userId,
@Nullable String packageName) {
@@ -2128,6 +2136,16 @@
}
}
+ @Override // Binder call
+ public int getPreferredWideGamutColorSpaceId() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getPreferredWideGamutColorSpaceIdInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
void setBrightness(int brightness) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index cfc85da..326984c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -34,6 +34,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
@@ -756,6 +757,14 @@
*/
private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
+ private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+ }
+
+ private static final UserManagerInternal sUserManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
private boolean mCopyOnWrite = false;
@NonNull
private String mEnabledInputMethodsStrCache = "";
@@ -833,7 +842,9 @@
if (mCopyOnWrite) {
mCopyOnWriteDataStore.put(key, str);
} else {
- Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+ final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+ Settings.Secure.putStringForUser(mResolver, key, str, userId);
}
}
@@ -852,7 +863,9 @@
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/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index f1de371..e6f0ed9 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -20,8 +20,6 @@
import android.location.Address;
import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
-import android.os.RemoteException;
-import android.util.Log;
import com.android.internal.os.BackgroundThread;
import com.android.server.ServiceWatcher;
@@ -68,35 +66,22 @@
public String getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- final String[] result = new String[]{"Service not Available"};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
- try {
- result[0] = provider.getFromLocation(
- latitude, longitude, maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return result[0];
+ return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
+ }, "Service not Available");
}
public String getFromLocationName(String locationName,
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- final String[] result = new String[]{"Service not Available"};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
- try {
- result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude,
- lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
- maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return result[0];
+ return provider.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
+ }, "Service not Available");
}
}
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index a6da8c5..6b5b1be 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -127,20 +127,16 @@
return mServiceWatcher.start();
}
- private void initializeService(IBinder binder) {
+ private void initializeService(IBinder binder) throws RemoteException {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
- try {
- service.setLocationProviderManager(mManager);
+ service.setLocationProviderManager(mManager);
- synchronized (mRequestLock) {
- if (mRequest != null) {
- service.setRequest(mRequest, mWorkSource);
- }
+ synchronized (mRequestLock) {
+ if (mRequest != null) {
+ service.setRequest(mRequest, mWorkSource);
}
- } catch (RemoteException e) {
- Log.w(TAG, e);
}
}
@@ -157,63 +153,44 @@
}
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- service.setRequest(request, source);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
+ service.setRequest(request, source);
});
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" service=" + mServiceWatcher);
- mServiceWatcher.runOnBinder(binder -> {
+ mServiceWatcher.runOnBinderBlocking(binder -> {
try {
TransferPipe.dumpAsync(binder, fd, args);
} catch (IOException | RemoteException e) {
- pw.println(" failed to dump location provider: " + e);
+ pw.println(" failed to dump location provider");
}
- });
+ return null;
+ }, null);
}
@Override
public int getStatus(Bundle extras) {
- int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- status[0] = service.getStatus(extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return status[0];
+ return service.getStatus(extras);
+ }, LocationProvider.TEMPORARILY_UNAVAILABLE);
}
@Override
public long getStatusUpdateTime() {
- long[] updateTime = new long[] {0L};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- updateTime[0] = service.getStatusUpdateTime();
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return updateTime[0];
+ return service.getStatusUpdateTime();
+ }, 0L);
}
@Override
public void sendExtraCommand(String command, Bundle extras) {
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- service.sendExtraCommand(command, extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
+ service.sendExtraCommand(command, extras);
});
}
}
diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS
index 92b4d5f..c2c95e6 100644
--- a/services/core/java/com/android/server/location/OWNERS
+++ b/services/core/java/com/android/server/location/OWNERS
@@ -1,6 +1,8 @@
+aadmal@google.com
arthuri@google.com
bduddie@google.com
gomo@google.com
sooniln@google.com
weiwa@google.com
wyattriley@google.com
+yuhany@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a164686..de3f50a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1087,7 +1087,8 @@
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
- || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+ || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
+ || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_ALL);
String pkgList[] = null;
@@ -1108,6 +1109,23 @@
uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
cancelNotifications = false;
unhideNotifications = true;
+ } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
+ final int distractionRestrictions =
+ intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS,
+ PackageManager.RESTRICTION_NONE);
+ if ((distractionRestrictions
+ & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ hideNotifications = true;
+ } else {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ unhideNotifications = true;
+ }
+
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1651,6 +1669,7 @@
IntentFilter suspendedPkgFilter = new IntentFilter();
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
suspendedPkgFilter, null, null);
@@ -7743,6 +7762,20 @@
mPackageIntentReceiver.onReceive(getContext(), intent);
}
+ @VisibleForTesting
+ protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
+ // only use for testing: mimic receive broadcast that package is (un)distracting
+ // but does not actually register that info with packagemanager
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+
+ final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
/**
* Wrapper for a StatusBarNotification object that allows transfer across a oneway
* binder without sending large amounts of data over a oneway transaction.
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 3d88f20..2aaa1ed 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -176,6 +176,14 @@
// only use for testing
mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
}
+ case "distract_package": {
+ // only use for testing
+ // Flag values are in
+ // {@link android.content.pm.PackageManager.DistractionRestriction}.
+ mDirectService.simulatePackageDistractionBroadcast(
+ Integer.parseInt(getNextArgRequired()),
+ getNextArgRequired().split(","));
+ }
break;
case "post":
case "notify":
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 692c032..6f1eeeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -39,6 +39,7 @@
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
@@ -258,6 +259,8 @@
return runSetHarmfulAppWarning();
case "get-harmful-app-warning":
return runGetHarmfulAppWarning();
+ case "get-stagedsessions":
+ return getStagedSessions();
case "uninstall-system-updates":
return uninstallSystemUpdates();
default: {
@@ -282,6 +285,28 @@
return -1;
}
+ private int getStagedSessions() {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ List<SessionInfo> stagedSessionsList =
+ mInterface.getPackageInstaller().getStagedSessions().getList();
+ for (SessionInfo session: stagedSessionsList) {
+ pw.println("appPackageName = " + session.getAppPackageName()
+ + "; sessionId = " + session.getSessionId()
+ + "; isStaged = " + session.isStaged()
+ + "; isSessionReady = " + session.isSessionReady()
+ + "; isSessionApplied = " + session.isSessionApplied()
+ + "; isSessionFailed = " + session.isSessionFailed() + ";");
+ }
+ } catch (RemoteException e) {
+ pw.println("Failure ["
+ + e.getClass().getName() + " - "
+ + e.getMessage() + "]");
+ return 0;
+ }
+ return 1;
+ }
+
private int uninstallSystemUpdates() {
final PrintWriter pw = getOutPrintWriter();
List<String> failedUninstalls = new LinkedList<>();
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
new file mode 100644
index 0000000..a2c8dac
--- /dev/null
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.attention.AttentionManagerInternal;
+import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for checking if the user is currently paying attention to the phone and
+ * notifying {@link PowerManagerService} that user activity should be renewed.
+ *
+ * This class also implements a limit of how long the extension should be, to avoid security
+ * issues where the device would never be locked.
+ */
+public class AttentionDetector {
+
+ private static final String TAG = "AttentionDetector";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Invoked whenever user attention is detected.
+ */
+ private final Runnable mOnUserAttention;
+
+ /**
+ * The maximum time, in millis, that the phone can stay unlocked because of attention events,
+ * triggered by any user.
+ */
+ @VisibleForTesting
+ protected long mMaximumExtensionMillis;
+
+ private final Object mLock;
+
+ /**
+ * {@link android.service.attention.AttentionService} API timeout.
+ */
+ private long mMaxAttentionApiTimeoutMillis;
+
+ /**
+ * Last known user activity.
+ */
+ private long mLastUserActivityTime;
+
+ @VisibleForTesting
+ protected AttentionManagerInternal mAttentionManager;
+
+ /**
+ * If we're currently waiting for an attention callback
+ */
+ private boolean mRequested;
+
+ /**
+ * Current wakefulness of the device. {@see PowerManagerInternal}
+ */
+ private int mWakefulness;
+
+ @VisibleForTesting
+ final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() {
+
+ @Override
+ public void onSuccess(int requestCode, int result, long timestamp) {
+ Slog.v(TAG, "onSuccess: " + requestCode + ", " + result
+ + " - current requestCode: " + getRequestCode());
+ synchronized (mLock) {
+ if (requestCode == getRequestCode() && mRequested) {
+ mRequested = false;
+ if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ if (DEBUG) Slog.d(TAG, "Device slept before receiving callback.");
+ return;
+ }
+ if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) {
+ mOnUserAttention.run();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int requestCode, int error) {
+ Slog.i(TAG, "Failed to check attention: " + error);
+ synchronized (mLock) {
+ if (requestCode == getRequestCode()) {
+ mRequested = false;
+ }
+ }
+ }
+ };
+
+ public AttentionDetector(Runnable onUserAttention, Object lock) {
+ mOnUserAttention = onUserAttention;
+ mLock = lock;
+ }
+
+ public void systemReady(Context context) {
+ mAttentionManager = LocalServices.getService(AttentionManagerInternal.class);
+ mMaximumExtensionMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_attentionMaximumExtension);
+ mMaxAttentionApiTimeoutMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_attentionApiTimeout);
+ }
+
+ public long updateUserActivity(long nextScreenDimming) {
+ if (!isAttentionServiceSupported()) {
+ return nextScreenDimming;
+ }
+
+ final long now = SystemClock.uptimeMillis();
+ final long whenToCheck = nextScreenDimming - getAttentionTimeout();
+ final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis;
+ if (now < whenToCheck) {
+ if (DEBUG) {
+ Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now));
+ }
+ return nextScreenDimming;
+ } else if (whenToStopExtending < whenToCheck) {
+ if (DEBUG) {
+ Slog.d(TAG, "Let device sleep to avoid false results and improve security "
+ + (whenToCheck - whenToStopExtending));
+ }
+ return nextScreenDimming;
+ } else if (mRequested) {
+ if (DEBUG) {
+ Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode());
+ }
+ return whenToCheck;
+ }
+
+ // Ideally we should attribute mRequested to the result of #checkAttention, but the
+ // callback might arrive before #checkAttention returns (if there are cached results.)
+ // This means that we must assume that the request was successful, and then cancel it
+ // afterwards if AttentionManager couldn't deliver it.
+ mRequested = true;
+ final boolean sent = mAttentionManager.checkAttention(getRequestCode(),
+ getAttentionTimeout(), mCallback);
+ if (!sent) {
+ mRequested = false;
+ }
+
+ Slog.v(TAG, "Checking user attention with request code: " + getRequestCode());
+ return whenToCheck;
+ }
+
+ /**
+ * Handles user activity by cancelling any pending attention requests and keeping track of when
+ * the activity happened.
+ *
+ * @param eventTime Activity time, in uptime millis.
+ * @param event Activity type as defined in {@link PowerManager}.
+ * @return 0 when activity was ignored, 1 when handled, -1 when invalid.
+ */
+ public int onUserActivity(long eventTime, int event) {
+ switch (event) {
+ case PowerManager.USER_ACTIVITY_EVENT_ATTENTION:
+ return 0;
+ case PowerManager.USER_ACTIVITY_EVENT_OTHER:
+ case PowerManager.USER_ACTIVITY_EVENT_BUTTON:
+ case PowerManager.USER_ACTIVITY_EVENT_TOUCH:
+ case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY:
+ cancelCurrentRequestIfAny();
+ mLastUserActivityTime = eventTime;
+ return 1;
+ default:
+ if (DEBUG) {
+ Slog.d(TAG, "Attention not reset. Unknown activity event: " + event);
+ }
+ return -1;
+ }
+ }
+
+ public void onWakefulnessChangeStarted(int wakefulness) {
+ mWakefulness = wakefulness;
+ if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ cancelCurrentRequestIfAny();
+ }
+ }
+
+ private void cancelCurrentRequestIfAny() {
+ if (mRequested) {
+ mAttentionManager.cancelAttentionCheck(getRequestCode());
+ mRequested = false;
+ }
+ }
+
+ @VisibleForTesting
+ int getRequestCode() {
+ return (int) (mLastUserActivityTime % Integer.MAX_VALUE);
+ }
+
+ @VisibleForTesting
+ long getAttentionTimeout() {
+ return mMaxAttentionApiTimeoutMillis;
+ }
+
+ /**
+ * {@see AttentionManagerInternal#isAttentionServiceSupported}
+ */
+ @VisibleForTesting
+ boolean isAttentionServiceSupported() {
+ return mAttentionManager.isAttentionServiceSupported();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.print("AttentionDetector:");
+ pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis);
+ pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis);
+ pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime);
+ pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported());
+ pw.print(" mRequested=" + mRequested);
+ }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a027873..3be6480 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -229,6 +229,7 @@
private final BatterySaverController mBatterySaverController;
private final BatterySaverStateMachine mBatterySaverStateMachine;
private final BatterySavingStats mBatterySavingStats;
+ private final AttentionDetector mAttentionDetector;
private final BinderService mBinderService;
private final LocalService mLocalService;
private final NativeWrapper mNativeWrapper;
@@ -736,6 +737,7 @@
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mBatterySavingStats = new BatterySavingStats(mLock);
mBatterySaverPolicy =
@@ -804,6 +806,7 @@
mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
mPolicy = getLocalService(WindowManagerPolicy.class);
mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ mAttentionDetector.systemReady(mContext);
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
@@ -1326,6 +1329,16 @@
}
}
+ private void onUserAttention() {
+ synchronized (mLock) {
+ if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
+ Process.SYSTEM_UID)) {
+ updatePowerStateLocked();
+ }
+ }
+ }
+
private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
@@ -1346,6 +1359,7 @@
}
mNotifier.onUserActivity(event, uid);
+ mAttentionDetector.onUserActivity(eventTime, event);
if (mUserInactiveOverrideFromWindowManager) {
mUserInactiveOverrideFromWindowManager = false;
@@ -1593,6 +1607,7 @@
if (mNotifier != null) {
mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
}
+ mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
}
}
@@ -2085,6 +2100,10 @@
nextTimeout = -1;
}
+ if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) {
+ nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout);
+ }
+
if (nextProfileTimeout > 0) {
nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
}
@@ -3477,6 +3496,7 @@
mBatterySaverPolicy.dump(pw);
mBatterySaverStateMachine.dump(pw);
+ mAttentionDetector.dump(pw);
pw.println();
final int numProfiles = mProfilePowerState.size();
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8b4c410..6487bd7 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,7 +28,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
import android.content.pm.VersionedPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.PackageRollbackInfo;
@@ -56,12 +55,10 @@
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
-import java.util.Set;
/**
* Implementation of service that manages APK level rollbacks.
@@ -200,48 +197,20 @@
}
@Override
- public RollbackInfo getAvailableRollback(String packageName) {
+ public ParceledListSlice getAvailableRollbacks() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLLBACKS,
- "getAvailableRollback");
+ "getAvailableRollbacks");
- RollbackData data = getRollbackForPackage(packageName);
- if (data == null) {
- return null;
- }
-
- // Note: The rollback for the package ought to be for the currently
- // installed version, otherwise the rollback data is out of date. In
- // that rare case, we'll check when we execute the rollback whether
- // it's out of date or not, so no need to check package versions here.
-
- for (PackageRollbackInfo info : data.packages) {
- if (info.getPackageName().equals(packageName)) {
- // TODO: Once the RollbackInfo API supports info about
- // dependant packages, add that info here.
- return new RollbackInfo(data.rollbackId, info);
- }
- }
- return null;
- }
-
- @Override
- public StringParceledListSlice getPackagesWithAvailableRollbacks() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLLBACKS,
- "getPackagesWithAvailableRollbacks");
-
- final Set<String> packageNames = new HashSet<>();
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
+ List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
- for (PackageRollbackInfo info : data.packages) {
- packageNames.add(info.getPackageName());
- }
+ rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
}
+ return new ParceledListSlice<>(rollbacks);
}
- return new StringParceledListSlice(new ArrayList<>(packageNames));
}
@Override
@@ -279,18 +248,11 @@
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
- String targetPackageName = rollback.targetPackage.getPackageName();
- Log.i(TAG, "Initiating rollback of " + targetPackageName);
+ Log.i(TAG, "Initiating rollback");
- // Get the latest RollbackData for the target package.
- final RollbackData data = getRollbackForPackage(targetPackageName);
+ RollbackData data = getRollbackForId(rollback.getRollbackId());
if (data == null) {
- sendFailure(statusReceiver, "No rollback available for package.");
- return;
- }
-
- if (data.rollbackId != rollback.getRollbackId()) {
- sendFailure(statusReceiver, "Rollback for package is out of date.");
+ sendFailure(statusReceiver, "Rollback unavailable");
return;
}
@@ -335,14 +297,8 @@
PackageManager pm = context.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
- String installerPackageName = pm.getInstallerPackageName(targetPackageName);
- if (installerPackageName == null) {
- sendFailure(statusReceiver, "Cannot find installer package");
- return;
- }
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- parentParams.setInstallerPackageName(installerPackageName);
parentParams.setAllowDowngrade(true);
parentParams.setMultiPackage();
int parentSessionId = packageInstaller.createSession(parentParams);
@@ -351,6 +307,11 @@
for (PackageRollbackInfo info : data.packages) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
+ if (installerPackageName == null) {
+ sendFailure(statusReceiver, "Cannot find installer package");
+ return;
+ }
params.setInstallerPackageName(installerPackageName);
params.setAllowDowngrade(true);
int sessionId = packageInstaller.createSession(params);
@@ -392,7 +353,7 @@
addRecentlyExecutedRollback(rollback);
sendSuccess(statusReceiver);
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
// TODO: This call emits the warning "Calling a method in the
// system process without a qualified user". Fix that.
@@ -406,7 +367,7 @@
data.inProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
- Log.e(TAG, "Unable to roll back " + targetPackageName, e);
+ Log.e(TAG, "Rollback failed", e);
sendFailure(statusReceiver, "IOException: " + e.toString());
return;
}
@@ -537,9 +498,12 @@
boolean changed = false;
while (iter.hasNext()) {
RollbackInfo rollback = iter.next();
- if (packageName.equals(rollback.targetPackage.getPackageName())) {
- iter.remove();
- changed = true;
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (packageName.equals(info.getPackageName())) {
+ iter.remove();
+ changed = true;
+ break;
+ }
}
}
@@ -935,6 +899,25 @@
return null;
}
+ /*
+ * Returns the RollbackData, if any, for an available rollback with the
+ * given rollbackId.
+ */
+ private RollbackData getRollbackForId(int rollbackId) {
+ synchronized (mLock) {
+ // TODO: Have ensureRollbackDataLoadedLocked return the list of
+ // available rollbacks, to hopefully avoid forgetting to call it?
+ ensureRollbackDataLoadedLocked();
+ for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+ RollbackData data = mAvailableRollbacks.get(i);
+ if (data.rollbackId == rollbackId) {
+ return data;
+ }
+ }
+ }
+ return null;
+ }
+
@GuardedBy("mLock")
private int allocateRollbackIdLocked() throws IOException {
int n = 0;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1f2f1cc..3954a11 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
+import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Handler;
@@ -51,11 +52,14 @@
@Override
public boolean onHealthCheckFailed(String packageName) {
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName);
- if (rollback != null) {
- // TODO(zezeozue): Only rollback if rollback version == failed package version
- mHandler.post(() -> executeRollback(rollbackManager, rollback));
- return true;
+ for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageName.equals(packageRollback.getPackageName())) {
+ // TODO(zezeozue): Only rollback if rollback version == failed package version
+ mHandler.post(() -> executeRollback(rollbackManager, rollback));
+ return true;
+ }
+ }
}
// Don't handle the notification, no rollbacks available
return false;
@@ -84,7 +88,7 @@
}
});
});
- manager.executeRollback(rollback, rollbackReceiver.getIntentSender());
+ manager.commitRollback(rollback, rollbackReceiver.getIntentSender());
}
@Override
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 7738be9..3b24b3e 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -114,13 +114,9 @@
for (int i = 0; i < array.length(); ++i) {
JSONObject element = array.getJSONObject(i);
int rollbackId = element.getInt("rollbackId");
- String packageName = element.getString("packageName");
- long higherVersionCode = element.getLong("higherVersionCode");
- long lowerVersionCode = element.getLong("lowerVersionCode");
- PackageRollbackInfo target = new PackageRollbackInfo(
- new VersionedPackage(packageName, higherVersionCode),
- new VersionedPackage(packageName, lowerVersionCode));
- RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+ List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
+ element.getJSONArray("packages"));
+ RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
recentlyExecutedRollbacks.add(rollback);
}
} catch (IOException | JSONException e) {
@@ -155,18 +151,8 @@
void saveAvailableRollback(RollbackData data) throws IOException {
try {
JSONObject dataJson = new JSONObject();
- JSONArray packagesJson = new JSONArray();
- for (PackageRollbackInfo info : data.packages) {
- JSONObject infoJson = new JSONObject();
- infoJson.put("packageName", info.getPackageName());
- infoJson.put("higherVersionCode",
- info.getVersionRolledBackFrom().getLongVersionCode());
- infoJson.put("lowerVersionCode",
- info.getVersionRolledBackTo().getVersionCode());
- packagesJson.put(infoJson);
- }
dataJson.put("rollbackId", data.rollbackId);
- dataJson.put("packages", packagesJson);
+ dataJson.put("packages", toJson(data.packages));
dataJson.put("timestamp", data.timestamp.toString());
PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
@@ -200,11 +186,7 @@
RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
JSONObject element = new JSONObject();
element.put("rollbackId", rollback.getRollbackId());
- element.put("packageName", rollback.targetPackage.getPackageName());
- element.put("higherVersionCode",
- rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
- element.put("lowerVersionCode",
- rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+ element.put("packages", toJson(rollback.getPackages()));
array.put(element);
}
@@ -229,18 +211,7 @@
int rollbackId = dataJson.getInt("rollbackId");
RollbackData data = new RollbackData(rollbackId, backupDir);
-
- JSONArray packagesJson = dataJson.getJSONArray("packages");
- for (int i = 0; i < packagesJson.length(); ++i) {
- JSONObject infoJson = packagesJson.getJSONObject(i);
- String packageName = infoJson.getString("packageName");
- long higherVersionCode = infoJson.getLong("higherVersionCode");
- long lowerVersionCode = infoJson.getLong("lowerVersionCode");
- data.packages.add(new PackageRollbackInfo(
- new VersionedPackage(packageName, higherVersionCode),
- new VersionedPackage(packageName, lowerVersionCode)));
- }
-
+ data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
data.timestamp = Instant.parse(dataJson.getString("timestamp"));
return data;
} catch (JSONException | DateTimeParseException e) {
@@ -248,6 +219,40 @@
}
}
+ private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put("packageName", info.getPackageName());
+ json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
+ json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
+ return json;
+ }
+
+ private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
+ String packageName = json.getString("packageName");
+ long higherVersionCode = json.getLong("higherVersionCode");
+ long lowerVersionCode = json.getLong("lowerVersionCode");
+ return new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode));
+ }
+
+ private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
+ JSONArray json = new JSONArray();
+ for (PackageRollbackInfo info : infos) {
+ json.put(toJson(info));
+ }
+ return json;
+ }
+
+ private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json)
+ throws JSONException {
+ List<PackageRollbackInfo> infos = new ArrayList<>();
+ for (int i = 0; i < json.length(); ++i) {
+ infos.add(packageRollbackInfoFromJson(json.getJSONObject(i)));
+ }
+ return infos;
+ }
+
/**
* Deletes a file completely.
* If the file is a directory, its contents are deleted as well.
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index acede7d..9135d1d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -246,6 +246,10 @@
@Nullable
private final KernelCpuThreadReader mKernelCpuThreadReader;
+ private long mDebugElapsedClockPreviousValue = 0;
+ private long mDebugElapsedClockPullCount = 0;
+ private long mDebugFailingElapsedClockPreviousValue = 0;
+ private long mDebugFailingElapsedClockPullCount = 0;
private BatteryStatsHelper mBatteryStatsHelper = null;
private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
@@ -1726,6 +1730,56 @@
}
}
+ private void pullDebugElapsedClock(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final long elapsedMillis = SystemClock.elapsedRealtime();
+ final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0
+ ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue;
+
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeLong(mDebugElapsedClockPullCount);
+ e.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e.writeLong(elapsedMillis);
+ e.writeLong(clockDiffMillis);
+ e.writeInt(1 /* always set */);
+ pulledData.add(e);
+
+ if (mDebugElapsedClockPullCount % 2 == 1) {
+ StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e2.writeLong(mDebugElapsedClockPullCount);
+ e2.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e2.writeLong(elapsedMillis);
+ e2.writeLong(clockDiffMillis);
+ e2.writeInt(2 /* set on odd pulls */);
+ pulledData.add(e2);
+ }
+
+ mDebugElapsedClockPullCount++;
+ mDebugElapsedClockPreviousValue = elapsedMillis;
+ }
+
+ private void pullDebugFailingElapsedClock(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ final long elapsedMillis = SystemClock.elapsedRealtime();
+ // Fails every 10 buckets.
+ if (mDebugFailingElapsedClockPullCount++ % 10 == 0) {
+ mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+ throw new RuntimeException("Failing debug elapsed clock");
+ }
+
+ e.writeLong(mDebugFailingElapsedClockPullCount);
+ e.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e.writeLong(elapsedMillis);
+ e.writeLong(mDebugFailingElapsedClockPreviousValue == 0
+ ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue);
+ mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+ pulledData.add(e);
+ }
+
/**
* Pulls various data.
*/
@@ -1892,6 +1946,14 @@
pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.DEBUG_ELAPSED_CLOCK: {
+ pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
+ pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 29645f6..ed029cd 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
+
import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT;
import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.AppWindowThumbnailProto.WIDTH;
@@ -82,7 +85,8 @@
.setName("thumbnail anim: " + appToken.toString())
.setBufferSize(mWidth, mHeight)
.setFormat(PixelFormat.TRANSLUCENT)
- .setMetadata(appToken.windowType,
+ .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType)
+ .setMetadata(METADATA_OWNER_UID,
window != null ? window.mOwnerUid : Binder.getCallingUid())
.build();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7dd55b..476bd6e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -22,6 +22,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.EMPTY;
+import static android.view.SurfaceControl.METADATA_TASK_ID;
import static com.android.server.EventLogTags.WM_TASK_REMOVED;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
@@ -643,6 +644,11 @@
return getAppAnimationLayer(ANIMATION_LAYER_HOME);
}
+ @Override
+ SurfaceControl.Builder makeSurface() {
+ return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
+ }
+
boolean isTaskAnimating() {
final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController();
if (recentsAnim != null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index c2a8e7e..dea3597 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -18,6 +18,8 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -35,7 +37,6 @@
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowContentFrameStats;
@@ -103,7 +104,8 @@
.setBufferSize(w, h)
.setFormat(format)
.setFlags(flags)
- .setMetadata(windowType, ownerUid);
+ .setMetadata(METADATA_WINDOW_TYPE, windowType)
+ .setMetadata(METADATA_OWNER_UID, ownerUid);
mSurfaceControl = b.build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
new file mode 100644
index 0000000..9f1cbcd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.attention.AttentionManagerInternal;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class AttentionDetectorTest extends AndroidTestCase {
+
+ private @Mock AttentionManagerInternal mAttentionManagerInternal;
+ private @Mock Runnable mOnUserAttention;
+ private TestableAttentionDetector mAttentionDetector;
+ private long mAttentionTimeout;
+ private long mNextDimming;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any()))
+ .thenReturn(true);
+ mAttentionDetector = new TestableAttentionDetector();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mAttentionDetector.setAttentionServiceSupported(true);
+ mNextDimming = SystemClock.uptimeMillis() + 3000L;
+ }
+
+ @Test
+ public void testOnUserActivity_checksAttention() {
+ long when = registerAttention();
+ verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnUserActivity_doesntCheckIfNotSupported() {
+ mAttentionDetector.setAttentionServiceSupported(false);
+ long when = registerAttention();
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(mNextDimming).isEqualTo(when);
+ }
+
+ @Test
+ public void onUserActivity_ignoresWhiteListedActivityTypes() {
+ for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) {
+ int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i);
+ if (result == -1) {
+ throw new AssertionError("User activity " + i + " isn't listed in"
+ + " AttentionDetector#onUserActivity. Please consider how this new activity"
+ + " type affects the attention service.");
+ }
+ }
+ }
+
+ @Test
+ public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() {
+ long now = SystemClock.uptimeMillis();
+ mNextDimming = now;
+ mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(mNextDimming + 5000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_ignoresAfterMaximumExtension() {
+ long now = SystemClock.uptimeMillis();
+ mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(now + 2000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_skipsIfAlreadyScheduled() {
+ registerAttention();
+ reset(mAttentionManagerInternal);
+ long when = mAttentionDetector.updateUserActivity(mNextDimming);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() {
+ registerAttention();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt());
+ }
+
+ @Test
+ public void testCallbackOnSuccess_ignoresIfNoAttention() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ @Test
+ public void testCallbackOnSuccess_callsCallback() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention).run();
+ }
+
+ @Test
+ public void testCallbackOnFailure_unregistersCurrentRequestCode() {
+ registerAttention();
+ mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ private long registerAttention() {
+ mAttentionTimeout = 4000L;
+ mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ return mAttentionDetector.updateUserActivity(mNextDimming);
+ }
+
+ private class TestableAttentionDetector extends AttentionDetector {
+
+ private boolean mAttentionServiceSupported;
+
+ TestableAttentionDetector() {
+ super(AttentionDetectorTest.this.mOnUserAttention, new Object());
+ mAttentionManager = mAttentionManagerInternal;
+ mMaximumExtensionMillis = 10000L;
+ }
+
+ void setAttentionServiceSupported(boolean supported) {
+ mAttentionServiceSupported = supported;
+ }
+
+ @Override
+ public boolean isAttentionServiceSupported() {
+ return mAttentionServiceSupported;
+ }
+
+ @Override
+ public long getAttentionTimeout() {
+ return mAttentionTimeout;
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6222923..9c6ab0a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3410,6 +3410,77 @@
}
@Test
+ public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() {
+ // Post 2 notifications from 2 packages
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ // on broadcast, hide one of the packages
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"});
+ ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
+ assertEquals(1, captorHide.getValue().size());
+ assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+
+ // on broadcast, unhide the package
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"});
+ ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
+ assertEquals(1, captorUnhide.getValue().size());
+ assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+ }
+
+ @Test
+ public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() {
+ // Post 2 notifications from 2 packages
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ // on broadcast, hide one of the packages
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"});
+ ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture());
+ assertEquals(2, captorHide.getValue().size());
+ assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+ assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName());
+
+ // on broadcast, unhide the package
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"});
+ ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture());
+ assertEquals(2, captorUnhide.getValue().size());
+ assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+ assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName());
+ }
+
+ @Test
+ public void testNoNotificationsHiddenOnDistractingPackageBroadcast() {
+ // post notification from this package
+ final NotificationRecord notif1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ mService.addNotification(notif1);
+
+ // on broadcast, nothing is hidden since no notifications are of package "test_package"
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"});
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+ assertEquals(0, captor.getValue().size());
+ }
+
+ @Test
public void testCanUseManagedServicesLowRamNoWatchNullPkg() {
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d777bf1..aaca18a 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,6 +16,8 @@
package android.telephony;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
@@ -2145,6 +2147,79 @@
}
/**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The prefixes is a list of prefix {@code String} separated by this delimiter.
+ * @hide
+ */
+ public static final String REGEX_PREFIX_DELIMITER = ",";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The success status to be added into the intent to be sent to the calling package.
+ * @hide
+ */
+ public static final int RESULT_STATUS_SUCCESS = 0;
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The timeout status to be added into the intent to be sent to the calling package.
+ * @hide
+ */
+ public static final int RESULT_STATUS_TIMEOUT = 1;
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * Intent extra key of the retrieved SMS message as a {@code String}.
+ * @hide
+ */
+ public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * Intent extra key of SMS retriever status, which indicates whether the request for the
+ * coming SMS message is SUCCESS or TIMEOUT
+ * @hide
+ */
+ public static final String EXTRA_STATUS = "android.telephony.extra.STATUS";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int}
+ * @hide
+ */
+ public static final String EXTRA_SIM_SUBSCRIPTION_ID =
+ "android.telephony.extra.SIM_SUBSCRIPTION_ID";
+
+ /**
+ * Create a single use app specific incoming SMS request for the calling package.
+ *
+ * This method returns a token that if included in a subsequent incoming SMS message, and the
+ * SMS message has a prefix from the given prefixes list, the provided {@code intent} will be
+ * sent with the SMS data to the calling package.
+ *
+ * The token is only good for one use within a reasonable amount of time. After an SMS has been
+ * received containing the token all subsequent SMS messages with the token will be routed as
+ * normal.
+ *
+ * An app can only have one request at a time, if the app already has a request pending it will
+ * be replaced with a new request.
+ *
+ * @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The
+ * matching SMS message should have at least one of the prefixes in the beginning of the
+ * message.
+ * @param intent this intent is sent when the matching SMS message is received.
+ * @return Token to include in an SMS message.
+ */
+ @Nullable
+ public String createAppSpecificSmsTokenWithPackageInfo(
+ @Nullable String prefixes, @NonNull PendingIntent intent) {
+ try {
+ ISms iccSms = getISmsServiceOrThrow();
+ return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
+ ActivityThread.currentPackageName(), prefixes, intent);
+
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
* Filters a bundle to only contain MMS config variables.
*
* This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e710e0e..98fc725 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6848,13 +6848,13 @@
/**
* Values used to return status for hasCarrierPrivileges call.
*/
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2;
/**
@@ -7056,6 +7056,7 @@
/** @hide */
@SystemApi
+ @TestApi
@SuppressLint("Doclava125")
public int checkCarrierPrivilegesForPackage(String pkgName) {
try {
diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java
new file mode 100644
index 0000000..ac4d17a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsException.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines an IMS-related exception that has been thrown while interacting with a
+ * device or carrier provided ImsService implementation.
+ * @hide
+ */
+@SystemApi
+public class ImsException extends Exception {
+
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int CODE_ERROR_UNSPECIFIED = 0;
+ /**
+ * The operation has failed because there is no {@link ImsService} available to service it. This
+ * may be due to an {@link ImsService} crash or other illegal state.
+ * <p>
+ * This is a temporary error and the operation may be retried until the connection to the
+ * {@link ImsService} is restored.
+ */
+ public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1;
+
+ /**
+ * This device or carrier configuration does not support IMS for this subscription.
+ * <p>
+ * This is a permanent configuration error and there should be no retry.
+ */
+ public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CODE_ERROR_", value = {
+ CODE_ERROR_UNSPECIFIED,
+ CODE_ERROR_SERVICE_UNAVAILABLE,
+ CODE_ERROR_UNSUPPORTED_OPERATION
+ })
+ public @interface ImsErrorCode {}
+
+ private int mCode = CODE_ERROR_UNSPECIFIED;
+
+ /**
+ * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code.
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ImsException(@Nullable String message) {
+ super(getMessage(message, CODE_ERROR_UNSPECIFIED));
+ }
+
+ /**
+ * A new {@link ImsException} that includes an {@link ImsErrorCode} error code.
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ImsException(@Nullable String message, @ImsErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ }
+
+ /**
+ * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a
+ * {@link Throwable} that contains the original error that was thrown to lead to this Exception.
+ * @param message an optional message to detail the error condition more specifically.
+ * @param cause the {@link Throwable} that caused this {@link ImsException} to be created.
+ */
+ public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ }
+
+ /**
+ * @return the IMS Error code that is associated with this {@link ImsException}.
+ */
+ public @ImsErrorCode int getCode() {
+ return mCode;
+ }
+
+ private static String getMessage(String message, int code) {
+ StringBuilder builder;
+ if (!TextUtils.isEmpty(message)) {
+ builder = new StringBuilder(message);
+ builder.append(" (code: ");
+ builder.append(code);
+ builder.append(")");
+ return builder.toString();
+ } else {
+ return "code: " + code;
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 5b2e635..eb99d5d 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -54,7 +54,7 @@
* registration and MmTel capability status callbacks, as well as query/modify user settings for the
* associated subscription.
*
- * @see #createForSubscriptionId(Context, int)
+ * @see #createForSubscriptionId(int)
* @hide
*/
@SystemApi
@@ -315,15 +315,12 @@
/**
* Create an instance of ImsManager for the subscription id specified.
*
- * @param context The context to create this ImsMmTelManager instance within.
* @param subId The ID of the subscription that this ImsMmTelManager will use.
* @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
- * @throws IllegalArgumentException if the subscription is invalid or
- * the subscription ID is not an active subscription.
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
- public static ImsMmTelManager createForSubscriptionId(Context context, int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)
- || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+ public static ImsMmTelManager createForSubscriptionId(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
}
@@ -331,7 +328,7 @@
}
/**
- * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead.
+ * Only visible for testing, use {@link #createForSubscriptionId(int)} instead.
* @hide
*/
@VisibleForTesting
@@ -341,7 +338,7 @@
/**
* Registers a {@link RegistrationCallback} with the system, which will provide registration
- * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use
+ * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use
* {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
* events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
*
@@ -354,13 +351,14 @@
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
* {@link CapabilityCallback} callback.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerImsRegistrationCallback(@CallbackExecutor Executor executor,
- @NonNull RegistrationCallback c) {
+ @NonNull RegistrationCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
}
@@ -372,6 +370,8 @@
getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -403,7 +403,7 @@
/**
* Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
* availability updates for the subscription specified in
- * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)}
+ * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)}
* can also be used to query this information at any time.
*
* Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
@@ -419,13 +419,14 @@
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
* {@link CapabilityCallback} callback.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull CapabilityCallback c) {
+ @NonNull CapabilityCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
}
@@ -437,6 +438,8 @@
getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -796,14 +799,6 @@
}
}
- private static SubscriptionManager getSubscriptionManager(Context context) {
- SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
- if (manager == null) {
- throw new RuntimeException("Could not find SubscriptionManager.");
- }
- return manager;
- }
-
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
ServiceManager.getService(Context.TELEPHONY_SERVICE));
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 086a765..b171f79 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -172,15 +172,13 @@
/**
* Create a new {@link ProvisioningManager} for the subscription specified.
- * @param context The context that this manager will use.
+ *
* @param subId The ID of the subscription that this ProvisioningManager will use.
* @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
- * @throws IllegalArgumentException if the subscription is invalid or
- * the subscription ID is not an active subscription.
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
- public static ProvisioningManager createForSubscriptionId(Context context, int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)
- || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+ public static ProvisioningManager createForSubscriptionId(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
}
@@ -202,18 +200,21 @@
* @see SubscriptionManager.OnSubscriptionsChangedListener
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or the subscription is invalid.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor,
- @NonNull Callback callback) {
+ @NonNull Callback callback) throws ImsException {
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -332,6 +333,7 @@
* @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
* @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setProvisioningStatusForCapability(
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
@@ -358,6 +360,7 @@
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean getProvisioningStatusForCapability(
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index f35e886..fea763e 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -21,8 +21,10 @@
/**
* This class defines a general IMS-related exception.
*
+ * @deprecated Use {@link android.telephony.ims.ImsException} instead.
* @hide
*/
+@Deprecated
public class ImsException extends Exception {
/**
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index a4eb424..58ebb15 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -560,4 +560,19 @@
* @param intent PendingIntent to be sent when an SMS is received containing the token.
*/
String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent);
+
+ /**
+ * Create an app-only incoming SMS request for the calling package.
+ *
+ * If an incoming text contains the token returned by this method the provided
+ * <code>PendingIntent</code> will be sent containing the SMS data.
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app.
+ * @param prefixes the caller provided prefixes
+ * @param intent PendingIntent to be sent when a SMS is received containing the token and one
+ * of the prefixes
+ */
+ String createAppSpecificSmsTokenWithPackageInfo(
+ int subId, String callingPkg, String prefixes, in PendingIntent intent);
}
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 1cdf44d..f2f2960 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -188,4 +188,10 @@
public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public String createAppSpecificSmsTokenWithPackageInfo(
+ int subId, String callingPkg, String prefixes, PendingIntent intent) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index 979d13a..ee02a72 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -35,7 +35,6 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := DexLoggerNativeTestLibrary
-LOCAL_MULTILIB := first
LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE)
@@ -44,8 +43,6 @@
include $(BUILD_SHARED_LIBRARY)
-dexloggertest_so := $(LOCAL_BUILT_MODULE)
-
# And a standalone native executable that we can exec.
include $(CLEAR_VARS)
@@ -73,11 +70,15 @@
android-support-test \
truth-prebuilt \
+# Include both versions of the .so if we have 2 arch
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := \
+ DexLoggerNativeTestLibrary \
+
# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
# native binaries.
LOCAL_JAVA_RESOURCE_FILES := \
$(dexloggertest_jar) \
- $(dexloggertest_so) \
- $(dexloggertest_executable)
+ $(dexloggertest_executable) \
include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d68769b..e92cc56 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -21,6 +21,7 @@
import android.app.UiAutomation;
import android.content.Context;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
@@ -147,7 +148,7 @@
String expectedNameHash =
"996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -170,7 +171,7 @@
String expectedNameHash =
"8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -307,6 +308,12 @@
return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
}
+ private String libraryPath(final String libraryName) {
+ // This may be deprecated. but it tells us the ABI of this process which is exactly what we
+ // want.
+ return "/lib/" + Build.CPU_ABI + "/" + libraryName;
+ }
+
private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
MessageDigest hasher = MessageDigest.getInstance("SHA-256");
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index 030641b..e10f866 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -29,7 +29,7 @@
/**
* A broadcast receiver that can be used to get
- * ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts.
+ * ACTION_ROLLBACK_COMMITTED broadcasts.
*/
class RollbackBroadcastReceiver extends BroadcastReceiver {
@@ -43,7 +43,7 @@
*/
RollbackBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
InstrumentationRegistry.getContext().registerReceiver(this, filter);
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 13ac4f0..7ab716f 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -31,11 +31,8 @@
import android.util.Log;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Ignore;
@@ -43,6 +40,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -86,8 +84,8 @@
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
- // Register a broadcast receiver for notification when the rollback is
- // done executing.
+ // Register a broadcast receiver for notification when the
+ // rollback has been committed.
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -99,12 +97,11 @@
// uninstalled and when rollback manager deletes the rollback. Fix it
// so that's not the case!
for (int i = 0; i < 5; ++i) {
- for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
- Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
- Thread.sleep(1000);
- break;
- }
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ if (rollback != null) {
+ Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
+ Thread.sleep(1000);
}
}
@@ -113,13 +110,11 @@
// between when the app is uninstalled and when the previously
// available rollback, if any, is removed.
Thread.sleep(1000);
- assertNull(rm.getAvailableRollback(TEST_APP_A));
- assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+ assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
- // There should be no recently executed rollbacks for this package.
- for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
- }
+ // There should be no recently committed rollbacks for this package.
+ assertNull(getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
// Install v1 of the app (without rollbacks enabled).
RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -134,10 +129,9 @@
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -156,15 +150,9 @@
assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
// Verify the recent rollback has been recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
@@ -202,28 +190,25 @@
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Rollback of B should not rollback A
RollbackTestUtils.rollback(rollbackB);
@@ -264,28 +249,23 @@
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
// Rollback of B should rollback A as well
RollbackTestUtils.rollback(rollbackB);
@@ -297,10 +277,10 @@
}
/**
- * Test that recently executed rollback data is properly persisted.
+ * Test that recently committed rollback data is properly persisted.
*/
@Test
- public void testRecentlyExecutedRollbackPersistence() throws Exception {
+ public void testRecentlyCommittedRollbackPersistence() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
@@ -319,37 +299,27 @@
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
// Roll back the app.
RollbackTestUtils.rollback(rollback);
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// Verify the recent rollback has been recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Reload the persisted data.
rm.reloadPersistedData();
// Verify the recent rollback is still recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -378,17 +348,16 @@
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Expire the rollback.
rm.expireRollbackForPackage(TEST_APP_A);
// The rollback should no longer be available.
- assertNull(rm.getAvailableRollback(TEST_APP_A));
- assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+ assertNull(getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A));
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -459,7 +428,8 @@
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
RollbackTestUtils.rollback(rollback);
processUserData(TEST_APP_A);
} finally {
@@ -469,12 +439,12 @@
/**
* Test restrictions on rollback broadcast sender.
- * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast.
+ * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
*/
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
@@ -516,18 +486,18 @@
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// Both test apps should now be available for rollback, and the
- // targetPackage returned for rollback should be correct.
+ // RollbackInfo returned for the rollbacks should be correct.
// TODO: See if there is a way to remove this race condition
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Executing rollback should roll back the correct package.
RollbackTestUtils.rollback(rollbackA);
@@ -558,21 +528,14 @@
RollbackManager rm = RollbackTestUtils.getRollbackManager();
try {
- rm.getAvailableRollback(TEST_APP_A);
+ rm.getAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
- rm.getPackagesWithAvailableRollbacks();
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.getRecentlyExecutedRollbacks();
+ rm.getRecentlyCommittedRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -581,7 +544,7 @@
try {
// TODO: What if the implementation checks arguments for non-null
// first? Then this test isn't valid.
- rm.executeRollback(null, null);
+ rm.commitRollback(null, null);
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -629,12 +592,9 @@
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// TEST_APP_A should now be available for rollback.
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
-
- // TODO: Test the dependent apps for rollback are correct once we
- // support that in the RollbackInfo API.
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollback);
// Rollback the app. It should cause both test apps to be rolled
// back.
@@ -642,14 +602,16 @@
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
- // We should not see a recent rollback listed for TEST_APP_B
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
- }
+ // We should see recent rollbacks listed for both A and B.
+ Thread.sleep(1000);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
- // TODO: Test the listed dependent apps for the recently executed
- // rollback are correct once we support that in the RollbackInfo
- // API.
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
+
+ assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -697,13 +659,13 @@
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes
for (int i = 0; i < 5; i++) {
@@ -724,4 +686,47 @@
RollbackTestUtils.dropShellPermissionIdentity();
}
}
+
+ // Helper function to test the value of a RollbackInfo with single package
+ private void assertRollbackInfoEquals(String packageName,
+ long versionRolledBackFrom, long versionRolledBackTo,
+ RollbackInfo info) {
+ assertNotNull(info);
+ assertEquals(1, info.getPackages().size());
+ assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
+ info.getPackages().get(0));
+ }
+
+ // Helper function to test that the given rollback info is a rollback for
+ // the atomic set {A2, B2} -> {A1, B1}.
+ private void assertRollbackInfoForAandB(RollbackInfo rollback) {
+ assertNotNull(rollback);
+ assertEquals(2, rollback.getPackages().size());
+ if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
+ } else {
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
+ }
+ }
+
+ // Helper function to return the RollbackInfo with a given package in the
+ // list of rollbacks. Throws an assertion failure if there is more than
+ // one such rollback info. Returns null if there are no such rollback
+ // infos.
+ private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
+ String packageName) {
+ RollbackInfo found = null;
+ for (RollbackInfo rollback : rollbacks) {
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (packageName.equals(info.getPackageName())) {
+ assertNull(found);
+ found = rollback;
+ break;
+ }
+ }
+ }
+ return found;
+ }
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index edb1355..f481897 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -90,12 +90,12 @@
}
/**
- * Execute the given rollback.
+ * Commit the given rollback.
* @throws AssertionError if the rollback fails.
*/
static void rollback(RollbackInfo rollback) throws InterruptedException {
RollbackManager rm = getRollbackManager();
- rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
+ rm.commitRollback(rollback, LocalIntentSender.getIntentSender());
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7783e10..8f75287 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -182,7 +182,8 @@
defaults: ["aapt2_defaults"],
data: [
"integration-tests/CompileTest/**/*",
- "integration-tests/CommandTests/**/*"
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*"
],
}
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 85f9080..7a74ba9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -284,6 +284,8 @@
// The table might be modified by below code.
auto converted_table = apk->GetResourceTable();
+ std::unordered_set<std::string> files_written;
+
// Resources
for (const auto& package : converted_table->packages) {
for (const auto& type : package->types) {
@@ -297,10 +299,14 @@
return 1;
}
- if (!serializer->SerializeFile(file, output_writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize file " << *file->path);
- return 1;
+ // Only serialize if we haven't seen this file before
+ if (files_written.insert(*file->path).second) {
+ if (!serializer->SerializeFile(file, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize file "
+ << *file->path);
+ return 1;
+ }
}
} // file
} // config_value
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 8da5bb8..3c0fe37 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -18,6 +18,7 @@
#include "LoadedApk.h"
#include "test/Test.h"
+#include "ziparchive/zip_archive.h"
using testing::Eq;
using testing::Ne;
@@ -103,4 +104,45 @@
EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
}
+TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
+ StdErrDiagnostics diag;
+ const std::string apk_path =
+ file::BuildPath({android::base::GetExecutableDirectory(),
+ "integration-tests", "ConvertTest", "duplicate_entries.apk"});
+
+ const std::string out_convert_apk = GetTestPath("out_convert.apk");
+ std::vector<android::StringPiece> convert_args = {
+ "-o", out_convert_apk,
+ "--output-format", "proto",
+ apk_path
+ };
+ ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0));
+
+ ZipArchiveHandle handle;
+ ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0));
+
+ void* cookie = nullptr;
+
+ ZipString prefix("res/theme/10");
+ int32_t result = StartIteration(handle, &cookie, &prefix, nullptr);
+
+ // If this is -5, that means we've found a duplicate entry and this test has failed
+ EXPECT_THAT(result, Eq(0));
+
+ // But if read succeeds, verify only one res/theme/10 entry
+ int count = 0;
+
+ // Can't pass nullptrs into Next()
+ ZipString zip_name;
+ ZipEntry zip_data;
+
+ while ((result = Next(cookie, &zip_data, &zip_name)) == 0) {
+ count++;
+ }
+
+ EndIteration(cookie);
+
+ EXPECT_THAT(count, Eq(1));
+}
+
} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
new file mode 100644
index 0000000..c558a33
--- /dev/null
+++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
Binary files differ
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
index 961b47c..cee0cd5 100644
--- a/tools/bit/aapt.cpp
+++ b/tools/bit/aapt.cpp
@@ -159,10 +159,11 @@
inspect_apk(Apk* apk, const string& filename)
{
// Load the manifest xml
- Command cmd("aapt");
+ Command cmd("aapt2");
cmd.AddArg("dump");
cmd.AddArg("xmltree");
cmd.AddArg(filename);
+ cmd.AddArg("--file");
cmd.AddArg("AndroidManifest.xml");
int err;
@@ -217,11 +218,11 @@
if (current != NULL) {
Attribute attr;
string str = match[2];
- size_t colon = str.find(':');
+ size_t colon = str.rfind(':');
if (colon == string::npos) {
attr.name = str;
} else {
- attr.ns = scope->namespaces[string(str, 0, colon)];
+ attr.ns.assign(str, 0, colon);
attr.name.assign(str, colon+1, string::npos);
}
attr.value = match[3];
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index a71cea1..860094ae 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -623,12 +623,13 @@
const string buildProduct = get_required_env("TARGET_PRODUCT", false);
const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
-
+ const string buildOut = get_out_dir();
chdir_or_exit(buildTop.c_str());
- const string buildDevice = get_build_var("TARGET_DEVICE", false);
- const string buildId = get_build_var("BUILD_ID", false);
- const string buildOut = get_out_dir();
+ BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+ const string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
+ const string buildId = buildVars.GetBuildVar("BUILD_ID", false);
// Get the modules for the targets
map<string,Module> modules;
@@ -661,6 +662,7 @@
string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
bool syncSystem = false;
bool alwaysSyncSystem = false;
+ vector<string> systemFiles;
vector<InstallApk> installApks;
for (size_t i=0; i<targets.size(); i++) {
Target* target = targets[i];
@@ -670,6 +672,7 @@
// System partition
if (starts_with(file, systemPath)) {
syncSystem = true;
+ systemFiles.push_back(file);
if (!target->build) {
// If a system partition target didn't get built then
// it won't change we will always need to do adb sync
@@ -692,6 +695,19 @@
get_directory_contents(systemPath, &systemFilesBefore);
}
+ if (systemFiles.size() > 0){
+ print_info("System files:");
+ for (size_t i=0; i<systemFiles.size(); i++) {
+ printf(" %s\n", systemFiles[i].c_str());
+ }
+ }
+ if (installApks.size() > 0){
+ print_info("APKs to install:");
+ for (size_t i=0; i<installApks.size(); i++) {
+ printf(" %s\n", installApks[i].file.filename.c_str());
+ }
+ }
+
//
// Build
//
@@ -798,7 +814,8 @@
for (size_t j=0; j<target->module.installed.size(); j++) {
string filename = target->module.installed[j];
- if (!ends_with(filename, ".apk")) {
+ // Apk in the data partition
+ if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) {
continue;
}
@@ -1004,13 +1021,16 @@
void
run_tab_completion(const string& word)
{
- const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+ const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
const string buildOut = get_out_dir();
-
chdir_or_exit(buildTop.c_str());
- string buildDevice = sniff_device_name(buildOut, buildProduct);
+ BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+ string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
map<string,Module> modules;
read_modules(buildOut, buildDevice, &modules, true);
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index 5a9ab22..6270913 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -21,6 +21,7 @@
#include "util.h"
#include <json/reader.h>
+#include <json/writer.h>
#include <json/value.h>
#include <fstream>
@@ -34,22 +35,118 @@
using namespace std;
-map<string,string> g_buildVars;
+static bool
+map_contains(const map<string,string>& m, const string& k, const string& v) {
+ map<string,string>::const_iterator it = m.find(k);
+ if (it == m.end()) {
+ return false;
+ }
+ return it->second == v;
+}
+
+static string
+make_cache_filename(const string& outDir)
+{
+ string filename(outDir);
+ return filename + "/.bit_cache";
+}
+
+BuildVars::BuildVars(const string& outDir, const string& buildProduct,
+ const string& buildVariant, const string& buildType)
+ :m_filename(),
+ m_cache()
+{
+ m_cache["TARGET_PRODUCT"] = buildProduct;
+ m_cache["TARGET_BUILD_VARIANT"] = buildVariant;
+ m_cache["TARGET_BUILD_TYPE"] = buildType;
+
+ // If we have any problems reading the file, that's ok, just do
+ // uncached calls to make / soong.
+
+ if (outDir == "") {
+ return;
+ }
+
+
+ m_filename = make_cache_filename(outDir);
+
+ std::ifstream stream(m_filename, std::ifstream::binary);
+
+ if (stream.fail()) {
+ return;
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (!reader.parse(stream, json)) {
+ return;
+ }
+
+ if (!json.isObject()) {
+ return;
+ }
+
+ map<string,string> cache;
+
+ vector<string> names = json.getMemberNames();
+ const int N = names.size();
+ for (int i=0; i<N; i++) {
+ const string& name = names[i];
+ const Json::Value& value = json[name];
+ if (!value.isString()) {
+ continue;
+ }
+ cache[name] = value.asString();
+ }
+
+ // If all of the base variables match, then we can use this cache. Otherwise, use our
+ // base one. The next time someone reads a value, the new one, with our base varaibles
+ // will be saved.
+ if (map_contains(cache, "TARGET_PRODUCT", buildProduct)
+ && map_contains(cache, "TARGET_BUILD_VARIANT", buildVariant)
+ && map_contains(cache, "TARGET_BUILD_TYPE", buildType)) {
+ m_cache = cache;
+ }
+}
+
+BuildVars::~BuildVars()
+{
+}
+
+void
+BuildVars::save()
+{
+ if (m_filename == "") {
+ return;
+ }
+
+ Json::StyledStreamWriter writer(" ");
+
+ Json::Value json(Json::objectValue);
+
+ for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) {
+ json[it->first] = it->second;
+ }
+
+ std::ofstream stream(m_filename, std::ofstream::binary);
+ writer.write(stream, json);
+}
string
-get_build_var(const string& name, bool quiet)
+BuildVars::GetBuildVar(const string& name, bool quiet)
{
int err;
- map<string,string>::iterator it = g_buildVars.find(name);
- if (it == g_buildVars.end()) {
+ map<string,string>::iterator it = m_cache.find(name);
+ if (it == m_cache.end()) {
Command cmd("build/soong/soong_ui.bash");
cmd.AddArg("--dumpvar-mode");
cmd.AddArg(name);
string output = trim(get_command_output(cmd, &err, quiet));
if (err == 0) {
- g_buildVars[name] = output;
+ m_cache[name] = output;
+ save();
return output;
} else {
return string();
@@ -59,38 +156,6 @@
}
}
-string
-sniff_device_name(const string& buildOut, const string& product)
-{
- string match("ro.build.product=" + product);
-
- string base(buildOut + "/target/product");
- DIR* dir = opendir(base.c_str());
- if (dir == NULL) {
- return string();
- }
-
- dirent* entry;
- while ((entry = readdir(dir)) != NULL) {
- if (entry->d_name[0] == '.') {
- continue;
- }
- if (entry->d_type == DT_DIR) {
- string filename(base + "/" + entry->d_name + "/system/build.prop");
- vector<string> lines;
- split_lines(&lines, read_file(filename));
- for (size_t i=0; i<lines.size(); i++) {
- if (lines[i] == match) {
- return entry->d_name;
- }
- }
- }
- }
-
- closedir(dir);
- return string();
-}
-
void
json_error(const string& filename, const char* error, bool quiet)
{
diff --git a/tools/bit/make.h b/tools/bit/make.h
index 1c9504d..db0b69f 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -31,16 +31,26 @@
vector<string> installed;
};
-string get_build_var(const string& name, bool quiet);
-
/**
- * Poke around in the out directory and try to find a device name that matches
- * our product. This is faster than running get_build_var and good enough for
- * tab completion.
- *
- * Returns the empty string if we can't find one.
+ * Class to encapsulate getting build variables. Caches the
+ * results if possible.
*/
-string sniff_device_name(const string& buildOut, const string& product);
+class BuildVars
+{
+public:
+ BuildVars(const string& outDir, const string& buildProduct,
+ const string& buildVariant, const string& buildType);
+ ~BuildVars();
+
+ string GetBuildVar(const string& name, bool quiet);
+
+private:
+ void save();
+
+ string m_filename;
+
+ map<string,string> m_cache;
+};
void read_modules(const string& buildOut, const string& buildDevice,
map<string,Module>* modules, bool quiet);
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
index 790e0b4..35feda1 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -116,6 +116,20 @@
}
void
+print_info(const char* format, ...)
+{
+ fputs(g_escapeBold, stdout);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stdout);
+ fputc('\n', stdout);
+}
+
+void
print_one_line(const char* format, ...)
{
if (g_stdoutIsTty) {
diff --git a/tools/bit/print.h b/tools/bit/print.h
index b6c3e9a..db6cf5f 100644
--- a/tools/bit/print.h
+++ b/tools/bit/print.h
@@ -33,6 +33,7 @@
void print_command(const Command& command);
void print_error(const char* format, ...);
void print_warning(const char* format, ...);
+void print_info(const char* format, ...);
void print_one_line(const char* format, ...);
void check_error(int err);