summaryrefslogtreecommitdiff
path: root/services/core/java/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android')
-rw-r--r--services/core/java/com/android/server/audio/AdiDeviceState.java213
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java121
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java154
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java253
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java355
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java35
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java13
-rw-r--r--services/core/java/com/android/server/pm/UserTypeDetails.java44
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java25
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java3
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java66
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java26
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/Transition.java11
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
16 files changed, 952 insertions, 395 deletions
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
new file mode 100644
index 000000000000..1c456a781f7a
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 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 static android.media.AudioSystem.DEVICE_NONE;
+import static android.media.AudioSystem.isBluetoothDevice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Objects;
+
+/**
+ * Class representing all devices that were previously or are currently connected. Data is
+ * persisted in {@link android.provider.Settings.Secure}
+ */
+/*package*/ final class AdiDeviceState {
+ private static final String TAG = "AS.AdiDeviceState";
+
+ private static final String SETTING_FIELD_SEPARATOR = ",";
+
+ @AudioDeviceInfo.AudioDeviceType
+ private final int mDeviceType;
+
+ private final int mInternalDeviceType;
+ @NonNull
+ private final String mDeviceAddress;
+ /** Unique device id from internal device type and address. */
+ private final Pair<Integer, String> mDeviceId;
+ private boolean mSAEnabled;
+ private boolean mHasHeadTracker = false;
+ private boolean mHeadTrackerEnabled;
+
+ /**
+ * Constructor
+ *
+ * @param deviceType external audio device type
+ * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the
+ * default conversion of the external type will be used
+ * @param address must be non-null for wireless devices
+ * @throws NullPointerException if a null address is passed for a wireless device
+ */
+ AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType,
+ int internalDeviceType,
+ @Nullable String address) {
+ mDeviceType = deviceType;
+ if (internalDeviceType != DEVICE_NONE) {
+ mInternalDeviceType = internalDeviceType;
+ } else {
+ mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType);
+
+ }
+ mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
+ address) : "";
+ mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
+ }
+
+ public Pair<Integer, String> getDeviceId() {
+ return mDeviceId;
+ }
+
+ @AudioDeviceInfo.AudioDeviceType
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ public int getInternalDeviceType() {
+ return mInternalDeviceType;
+ }
+
+ @NonNull
+ public String getDeviceAddress() {
+ return mDeviceAddress;
+ }
+
+ public void setSAEnabled(boolean sAEnabled) {
+ mSAEnabled = sAEnabled;
+ }
+
+ public boolean isSAEnabled() {
+ return mSAEnabled;
+ }
+
+ public void setHeadTrackerEnabled(boolean headTrackerEnabled) {
+ mHeadTrackerEnabled = headTrackerEnabled;
+ }
+
+ public boolean isHeadTrackerEnabled() {
+ return mHeadTrackerEnabled;
+ }
+
+ public void setHasHeadTracker(boolean hasHeadTracker) {
+ mHasHeadTracker = hasHeadTracker;
+ }
+
+
+ public boolean hasHeadTracker() {
+ return mHasHeadTracker;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ // type check and cast
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final AdiDeviceState sads = (AdiDeviceState) obj;
+ return mDeviceType == sads.mDeviceType
+ && mInternalDeviceType == sads.mInternalDeviceType
+ && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull
+ && mSAEnabled == sads.mSAEnabled
+ && mHasHeadTracker == sads.mHasHeadTracker
+ && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled,
+ mHasHeadTracker, mHeadTrackerEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "type: " + mDeviceType
+ + " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
+ + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled
+ + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
+ }
+
+ public String toPersistableString() {
+ return (new StringBuilder().append(mDeviceType)
+ .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
+ .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0")
+ .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
+ .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
+ .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType)
+ .toString());
+ }
+
+ /**
+ * Gets the max size (including separators) when persisting the elements with
+ * {@link AdiDeviceState#toPersistableString()}.
+ */
+ public static int getPeristedMaxSize() {
+ return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+ + (SETTINGS_FIELD_SEPARATOR)5 */
+ }
+
+ @Nullable
+ public static AdiDeviceState fromPersistedString(@Nullable String persistedString) {
+ if (persistedString == null) {
+ return null;
+ }
+ if (persistedString.isEmpty()) {
+ return null;
+ }
+ String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
+ // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal
+ // device type
+ if (fields.length != 5 && fields.length != 6) {
+ // expecting all fields, fewer may mean corruption, ignore those settings
+ return null;
+ }
+ try {
+ final int deviceType = Integer.parseInt(fields[0]);
+ int internalDeviceType = -1;
+ if (fields.length == 6) {
+ internalDeviceType = Integer.parseInt(fields[5]);
+ }
+ final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
+ internalDeviceType, fields[1]);
+ deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
+ deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
+ deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
+ return deviceState;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
+ return null;
+ }
+ }
+
+ public AudioDeviceAttributes getAudioDeviceAttributes() {
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ mDeviceType, mDeviceAddress);
+ }
+
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 32c033230a16..054a26f9c0fb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -50,6 +50,7 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -69,8 +70,11 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
-/** @hide */
-/*package*/ final class AudioDeviceBroker {
+/**
+ * @hide
+ * (non final for mocking/spying)
+ */
+public class AudioDeviceBroker {
private static final String TAG = "AS.AudioDeviceBroker";
@@ -1149,8 +1153,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/*package*/ void registerStrategyPreferredDevicesDispatcher(
- @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
- mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
+ @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1159,8 +1163,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/*package*/ void registerStrategyNonDefaultDevicesDispatcher(
- @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
- mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+ @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1178,8 +1182,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
- @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
- mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+ mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged);
}
/*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1187,6 +1191,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
}
+ /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+ List<AudioDeviceAttributes> devices) {
+ return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices);
+ }
+
/*package*/ void registerCommunicationDeviceDispatcher(
@NonNull ICommunicationDeviceDispatcher dispatcher) {
mCommDevDispatchers.register(dispatcher);
@@ -1850,6 +1859,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
+ case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
+ onPersistAudioDeviceSettings();
+ break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1927,6 +1939,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+ private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
@@ -2354,4 +2368,95 @@ import java.util.concurrent.atomic.AtomicBoolean;
info.getId(),
null /*mixerAttributes*/);
}
+
+ /**
+ * post a message to persist the audio device settings.
+ * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
+ * init time for instance)
+ * Note this method is made public to work around a Mockito bug where it needs to be public
+ * in order to be mocked by a test a the same package
+ * (see https://code.google.com/archive/p/mockito/issues/127)
+ */
+ public void persistAudioDeviceSettings() {
+ sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
+ }
+
+ void onPersistAudioDeviceSettings() {
+ final String deviceSettings = mDeviceInventory.getDeviceSettings();
+ Log.v(TAG, "saving audio device settings: " + deviceSettings);
+ final SettingsAdapter settings = mAudioService.getSettings();
+ boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
+ Settings.Secure.AUDIO_DEVICE_INVENTORY,
+ deviceSettings, UserHandle.USER_CURRENT);
+ if (!res) {
+ Log.e(TAG, "error saving audio device settings: " + deviceSettings);
+ }
+ }
+
+ void onReadAudioDeviceSettings() {
+ final SettingsAdapter settingsAdapter = mAudioService.getSettings();
+ final ContentResolver contentResolver = mAudioService.getContentResolver();
+ String settings = settingsAdapter.getSecureStringForUser(contentResolver,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
+ if (settings == null) {
+ Log.i(TAG, "reading spatial audio device settings from legacy key"
+ + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+ // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like
+ // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid
+ // device settings when calling {@link #setDeviceSettings()}
+ settings = settingsAdapter.getSecureStringForUser(contentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
+ if (settings == null) {
+ Log.i(TAG, "no spatial audio device settings stored with legacy key");
+ } else if (!settings.equals("")) {
+ // Delete old key value and update the new key
+ if (!settingsAdapter.putSecureStringForUser(contentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED,
+ /*value=*/"",
+ UserHandle.USER_CURRENT)) {
+ Log.w(TAG, "cannot erase the legacy audio device settings with key "
+ + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+ }
+ if (!settingsAdapter.putSecureStringForUser(contentResolver,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY,
+ settings,
+ UserHandle.USER_CURRENT)) {
+ Log.e(TAG, "error updating the new audio device settings with key "
+ + Settings.Secure.AUDIO_DEVICE_INVENTORY);
+ }
+ }
+ }
+
+ if (settings != null && !settings.equals("")) {
+ setDeviceSettings(settings);
+ }
+ }
+
+ void setDeviceSettings(String settings) {
+ mDeviceInventory.setDeviceSettings(settings);
+ }
+
+ /** Test only method. */
+ String getDeviceSettings() {
+ return mDeviceInventory.getDeviceSettings();
+ }
+
+ List<AdiDeviceState> getImmutableDeviceInventory() {
+ return mDeviceInventory.getImmutableDeviceInventory();
+ }
+
+ void addDeviceStateToInventory(AdiDeviceState deviceState) {
+ mDeviceInventory.addDeviceStateToInventory(deviceState);
+ }
+
+ AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+ int canonicalType) {
+ return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType);
+ }
+
+ //------------------------------------------------
+ // for testing purposes only
+ void clearDeviceInventory() {
+ mDeviceInventory.clearDeviceInventory();
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f98809..5332acae13ad 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,6 +15,8 @@
*/
package com.android.server.audio;
+import static android.media.AudioSystem.isBluetoothDevice;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
@@ -59,6 +61,7 @@ import com.google.android.collect.Sets;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -78,12 +81,82 @@ public class AudioDeviceInventory {
private static final String TAG = "AS.AudioDeviceInventory";
+ private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
+ private static final String SETTING_DEVICE_SEPARATOR = "\\|";
+
// lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
private final Object mDevicesLock = new Object();
//Audio Analytics ids.
private static final String mMetricsId = "audio.device.";
+ private final Object mDeviceInventoryLock = new Object();
+ @GuardedBy("mDeviceInventoryLock")
+ private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
+
+ List<AdiDeviceState> getImmutableDeviceInventory() {
+ synchronized (mDeviceInventoryLock) {
+ return new ArrayList<AdiDeviceState>(mDeviceInventory.values());
+ }
+ }
+
+ void addDeviceStateToInventory(AdiDeviceState deviceState) {
+ synchronized (mDeviceInventoryLock) {
+ mDeviceInventory.put(deviceState.getDeviceId(), deviceState);
+ }
+ }
+
+ /**
+ * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
+ * Bluetooth device and no corresponding entry already exists.
+ * @param ada the device to add if needed
+ */
+ void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
+ if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
+ return;
+ }
+ synchronized (mDeviceInventoryLock) {
+ if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
+ return;
+ }
+ AdiDeviceState ads = new AdiDeviceState(
+ ada.getType(), ada.getInternalType(), ada.getAddress());
+ mDeviceInventory.put(ads.getDeviceId(), ads);
+ }
+ mDeviceBroker.persistAudioDeviceSettings();
+ }
+
+ /**
+ * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device
+ * type. Note: currently this method only returns a valid device for A2DP and BLE devices.
+ *
+ * @param ada attributes of device to match
+ * @param canonicalDeviceType external device type to match
+ * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or
+ * {@code null} otherwise.
+ */
+ @Nullable
+ AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+ int canonicalDeviceType) {
+ final boolean isWireless = isBluetoothDevice(ada.getInternalType());
+ synchronized (mDeviceInventoryLock) {
+ for (AdiDeviceState deviceState : mDeviceInventory.values()) {
+ if (deviceState.getDeviceType() == canonicalDeviceType
+ && (!isWireless || ada.getAddress().equals(
+ deviceState.getDeviceAddress()))) {
+ return deviceState;
+ }
+ }
+ }
+ return null;
+ }
+
+ void clearDeviceInventory() {
+ synchronized (mDeviceInventoryLock) {
+ mDeviceInventory.clear();
+ }
+ }
+
// List of connected devices
// Key for map created from DeviceInfo.makeDeviceListKey()
@GuardedBy("mDevicesLock")
@@ -341,6 +414,12 @@ public class AudioDeviceInventory {
mAppliedPresetRolesInt.forEach((key, devices) -> {
pw.println(" " + prefix + "preset: " + key.first
+ " role:" + key.second + " devices:" + devices); });
+ pw.println("\ndevices:\n");
+ synchronized (mDeviceInventoryLock) {
+ for (AdiDeviceState device : mDeviceInventory.values()) {
+ pw.println("\t" + device + "\n");
+ }
+ }
}
//------------------------------------------------------------
@@ -884,8 +963,8 @@ public class AudioDeviceInventory {
/*package*/ void registerStrategyPreferredDevicesDispatcher(
- @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
- mPrefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mPrefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -894,8 +973,8 @@ public class AudioDeviceInventory {
}
/*package*/ void registerStrategyNonDefaultDevicesDispatcher(
- @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
- mNonDefDevDispatchers.register(dispatcher);
+ @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+ mNonDefDevDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -976,8 +1055,8 @@ public class AudioDeviceInventory {
}
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
- @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
- mDevRoleCapturePresetDispatchers.register(dispatcher);
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+ mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
}
/*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1198,7 +1277,7 @@ public class AudioDeviceInventory {
AudioDeviceInfo device = Stream.of(connectedDevices)
.filter(d -> d.getInternalType() == ada.getInternalType())
- .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+ .filter(d -> (!isBluetoothDevice(d.getInternalType())
|| (d.getAddress().equals(ada.getAddress()))))
.findFirst()
.orElse(null);
@@ -1304,6 +1383,8 @@ public class AudioDeviceInventory {
updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
if (!connect) {
purgeDevicesRoles_l();
+ } else {
+ addAudioDeviceInInventoryIfNeeded(attributes);
}
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1570,6 +1651,7 @@ public class AudioDeviceInventory {
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -1619,7 +1701,7 @@ public class AudioDeviceInventory {
}
for (DeviceInfo di : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+ if (!isBluetoothDevice(di.mDeviceType)) {
continue;
}
AudioDeviceAttributes ada =
@@ -1733,7 +1815,7 @@ public class AudioDeviceInventory {
}
HashSet<String> processedAddresses = new HashSet<>(0);
for (DeviceInfo di : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+ if (!isBluetoothDevice(di.mDeviceType)
|| processedAddresses.contains(di.mDeviceAddress)) {
continue;
}
@@ -1743,7 +1825,7 @@ public class AudioDeviceInventory {
+ di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
}
for (DeviceInfo di2 : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+ if (!isBluetoothDevice(di2.mDeviceType)
|| !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
continue;
}
@@ -1869,9 +1951,9 @@ public class AudioDeviceInventory {
final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
-
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+ mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
@@ -1881,6 +1963,7 @@ public class AudioDeviceInventory {
mDeviceBroker.postApplyVolumeOnDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
+ addAudioDeviceInInventoryIfNeeded(ada);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -1983,6 +2066,7 @@ public class AudioDeviceInventory {
sensorUuid));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+ addAudioDeviceInInventoryIfNeeded(ada);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2313,6 +2397,9 @@ public class AudioDeviceInventory {
final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; i++) {
try {
+ if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
+ devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+ }
mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
strategy, devices);
} catch (RemoteException e) {
@@ -2326,6 +2413,9 @@ public class AudioDeviceInventory {
final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; i++) {
try {
+ if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) {
+ devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+ }
mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
strategy, devices);
} catch (RemoteException e) {
@@ -2339,6 +2429,9 @@ public class AudioDeviceInventory {
final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; ++i) {
try {
+ if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
+ devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+ }
mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
capturePreset, role, devices);
} catch (RemoteException e) {
@@ -2359,6 +2452,41 @@ public class AudioDeviceInventory {
}
}
+ /*package*/ String getDeviceSettings() {
+ int deviceCatalogSize = 0;
+ synchronized (mDeviceInventoryLock) {
+ deviceCatalogSize = mDeviceInventory.size();
+
+ final StringBuilder settingsBuilder = new StringBuilder(
+ deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+
+ Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
+ if (iterator.hasNext()) {
+ settingsBuilder.append(iterator.next().toPersistableString());
+ }
+ while (iterator.hasNext()) {
+ settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
+ settingsBuilder.append(iterator.next().toPersistableString());
+ }
+ return settingsBuilder.toString();
+ }
+ }
+
+ /*package*/ void setDeviceSettings(String settings) {
+ clearDeviceInventory();
+ String[] devSettings = TextUtils.split(Objects.requireNonNull(settings),
+ SETTING_DEVICE_SEPARATOR);
+ // small list, not worth overhead of Arrays.stream(devSettings)
+ for (String setting : devSettings) {
+ AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting);
+ // Note if the device is not compatible with spatialization mode or the device
+ // type is not canonical, it will be ignored in {@link SpatializerHelper}.
+ if (devState != null) {
+ addDeviceStateToInventory(devState);
+ }
+ }
+ }
+
//----------------------------------------------------------
// For tests only
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53ed38edffe4..d82cef546315 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -381,7 +381,6 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
private static final int MSG_ROUTING_UPDATED = 41;
private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
- private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43;
private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
@@ -1021,6 +1020,8 @@ public class AudioService extends IAudioService.Stub
mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
+
mIsSingleVolume = AudioSystem.isSingleVolume(context);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1033,13 +1034,14 @@ public class AudioService extends IAudioService.Stub
mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
- final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+ boolean binauralEnabledDefault = SystemProperties.getBoolean(
"ro.audio.spatializer_binaural_enabled_default", true);
- final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+ boolean transauralEnabledDefault = SystemProperties.getBoolean(
"ro.audio.spatializer_transaural_enabled_default", true);
- final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
+ boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
- mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker,
binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1207,8 +1209,6 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
-
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -2831,8 +2831,11 @@ public class AudioService extends IAudioService.Stub
if (devices == null) {
return AudioSystem.ERROR;
}
+
+ devices = retrieveBluetoothAddresses(devices);
+
final String logString = String.format(
- "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+ "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), strategy,
devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2888,7 +2891,7 @@ public class AudioService extends IAudioService.Stub
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -2903,6 +2906,9 @@ public class AudioService extends IAudioService.Stub
@NonNull AudioDeviceAttributes device) {
super.setDeviceAsNonDefaultForStrategy_enforcePermission();
Objects.requireNonNull(device);
+
+ device = retrieveBluetoothAddress(device);
+
final String logString = String.format(
"setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
@@ -2929,6 +2935,9 @@ public class AudioService extends IAudioService.Stub
AudioDeviceAttributes device) {
super.removeDeviceAsNonDefaultForStrategy_enforcePermission();
Objects.requireNonNull(device);
+
+ device = retrieveBluetoothAddress(device);
+
final String logString = String.format(
"removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString());
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2963,7 +2972,7 @@ public class AudioService extends IAudioService.Stub
status, strategy));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -2976,7 +2985,8 @@ public class AudioService extends IAudioService.Stub
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3000,7 +3010,8 @@ public class AudioService extends IAudioService.Stub
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+ mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3016,7 +3027,7 @@ public class AudioService extends IAudioService.Stub
}
/**
- * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+ * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
*/
public int setPreferredDevicesForCapturePreset(
int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3035,6 +3046,8 @@ public class AudioService extends IAudioService.Stub
return AudioSystem.ERROR;
}
+ devices = retrieveBluetoothAddresses(devices);
+
final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
capturePreset, devices);
if (status != AudioSystem.SUCCESS) {
@@ -3081,7 +3094,7 @@ public class AudioService extends IAudioService.Stub
status, capturePreset));
return new ArrayList<AudioDeviceAttributes>();
} else {
- return devices;
+ return anonymizeAudioDeviceAttributesList(devices);
}
}
@@ -3095,7 +3108,8 @@ public class AudioService extends IAudioService.Stub
return;
}
enforceModifyAudioRoutingPermission();
- mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+ dispatcher, isBluetoothPrividged());
}
/**
@@ -3115,7 +3129,9 @@ public class AudioService extends IAudioService.Stub
public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes) {
enforceQueryStateOrModifyRoutingPermission();
- return getDevicesForAttributesInt(attributes, false /* forVolume */);
+
+ return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+ getDevicesForAttributesInt(attributes, false /* forVolume */)));
}
/** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
@@ -3125,7 +3141,8 @@ public class AudioService extends IAudioService.Stub
*/
public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
@NonNull AudioAttributes attributes) {
- return getDevicesForAttributesInt(attributes, false /* forVolume */);
+ return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+ getDevicesForAttributesInt(attributes, false /* forVolume */)));
}
/**
@@ -6567,6 +6584,10 @@ public class AudioService extends IAudioService.Stub
return mContentResolver;
}
+ /*package*/ SettingsAdapter getSettings() {
+ return mSettings;
+ }
+
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -7314,6 +7335,8 @@ public class AudioService extends IAudioService.Stub
Objects.requireNonNull(device);
AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
+ device = retrieveBluetoothAddress(device);
+
sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
+ device.getAddress() + " behavior:"
@@ -7397,6 +7420,8 @@ public class AudioService extends IAudioService.Stub
// verify parameters
Objects.requireNonNull(device);
+ device = retrieveBluetoothAddress(device);
+
return getDeviceVolumeBehaviorInt(device);
}
@@ -7471,9 +7496,12 @@ public class AudioService extends IAudioService.Stub
/**
* see AudioManager.setWiredDeviceConnectionState()
*/
- public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+ public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes,
@ConnectionState int state, String caller) {
super.setWiredDeviceConnectionState_enforcePermission();
+ Objects.requireNonNull(attributes);
+
+ attributes = retrieveBluetoothAddress(attributes);
if (state != CONNECTION_STATE_CONNECTED
&& state != CONNECTION_STATE_DISCONNECTED) {
@@ -7514,6 +7542,9 @@ public class AudioService extends IAudioService.Stub
boolean connected) {
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
+
+ device = retrieveBluetoothAddress(device);
+
mDeviceBroker.setTestDeviceConnectionState(device,
connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
// simulate a routing update from native
@@ -9209,10 +9240,6 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.onInitSensors();
break;
- case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS:
- onPersistSpatialAudioDeviceSettings();
- break;
-
case MSG_RESET_SPATIALIZER:
mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
break;
@@ -10269,39 +10296,103 @@ public class AudioService extends IAudioService.Stub
}
void onInitSpatializer() {
- final String settings = mSettings.getSecureStringForUser(mContentResolver,
- Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
- if (settings == null) {
- Log.e(TAG, "error reading spatial audio device settings");
- }
- mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
+ mDeviceBroker.onReadAudioDeviceSettings();
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
+ private boolean isBluetoothPrividged() {
+ return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.BLUETOOTH_CONNECT)
+ || Binder.getCallingUid() == Process.SYSTEM_UID;
+ }
+
+ List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) {
+ if (isBluetoothPrividged()) {
+ return devices;
+ }
+
+ List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>();
+ for (AudioDeviceAttributes ada : devices) {
+ if (ada == null) {
+ continue;
+ }
+ checkedDevices.add(retrieveBluetoothAddressUncheked(ada));
+ }
+ return checkedDevices;
+ }
+
+ AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) {
+ if (isBluetoothPrividged()) {
+ return ada;
+ }
+ return retrieveBluetoothAddressUncheked(ada);
+ }
+
+ AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
+ Objects.requireNonNull(ada);
+ if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+ String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
+ for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
+ if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
+ && (ada.getInternalType() == ads.getInternalDeviceType())
+ && anonymizedAddress.equals(anonymizeBluetoothAddress(
+ ads.getDeviceAddress())))) {
+ continue;
+ }
+ ada.setAddress(ads.getDeviceAddress());
+ break;
+ }
+ }
+ return ada;
+ }
+
/**
- * post a message to persist the spatial audio device settings.
- * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
- * init time for instance)
- * Note this method is made public to work around a Mockito bug where it needs to be public
- * in order to be mocked by a test a the same package
- * (see https://code.google.com/archive/p/mockito/issues/127)
- */
- public void persistSpatialAudioDeviceSettings() {
- sendMsg(mAudioHandler,
- MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS,
- SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG,
- /*delay*/ 1000);
+ * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
+ * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
+ * @param address Mac address to be anonymized
+ * @return anonymized mac address
+ */
+ static String anonymizeBluetoothAddress(String address) {
+ if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
+ return null;
+ }
+ return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
}
- void onPersistSpatialAudioDeviceSettings() {
- final String settings = mSpatializerHelper.getSADeviceSettings();
- Log.v(TAG, "saving spatial audio device settings: " + settings);
- boolean res = mSettings.putSecureStringForUser(mContentResolver,
- Settings.Secure.SPATIAL_AUDIO_ENABLED,
- settings, UserHandle.USER_CURRENT);
- if (!res) {
- Log.e(TAG, "error saving spatial audio device settings: " + settings);
+ private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
+ List<AudioDeviceAttributes> devices) {
+ if (isBluetoothPrividged()) {
+ return devices;
}
+ return anonymizeAudioDeviceAttributesListUnchecked(devices);
+ }
+
+ /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+ List<AudioDeviceAttributes> devices) {
+ List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>();
+ for (AudioDeviceAttributes ada : devices) {
+ anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada));
+ }
+ return anonymizedDevices;
+ }
+
+ private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
+ AudioDeviceAttributes ada) {
+ if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+ return ada;
+ }
+ AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
+ res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
+ return res;
+ }
+
+ private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ if (isBluetoothPrividged()) {
+ return ada;
+ }
+
+ return anonymizeAudioDeviceAttributesUnchecked(ada);
}
//==========================================================================================
@@ -10351,13 +10442,16 @@ public class AudioService extends IAudioService.Stub
Objects.requireNonNull(usages);
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
+
+ final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
if (timeOutMs <= 0 || usages.length == 0) {
throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
}
Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
+ " usages:" + Arrays.toString(usages));
- if (mDeviceBroker.isDeviceConnected(device)) {
+ if (mDeviceBroker.isDeviceConnected(ada)) {
// not throwing an exception as there could be a race between a connection (server-side,
// notification of connection in flight) and a mute operation (client-side)
Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
@@ -10369,12 +10463,19 @@ public class AudioService extends IAudioService.Stub
+ mMutingExpectedDevice);
throw new IllegalStateException("muteAwaitConnection already in progress");
}
- mMutingExpectedDevice = device;
+ mMutingExpectedDevice = ada;
mMutedUsagesAwaitingConnection = usages;
- mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+ mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs);
}
- dispatchMuteAwaitConnection(cb -> { try {
- cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
+ AudioDeviceAttributes dev = ada;
+ if (!isPrivileged) {
+ dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+ }
+ cb.dispatchOnMutedUntilConnection(dev, usages);
+ } catch (RemoteException e) { }
+ });
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -10383,7 +10484,7 @@ public class AudioService extends IAudioService.Stub
super.getMutingExpectedDevice_enforcePermission();
synchronized (mMuteAwaitConnectionLock) {
- return mMutingExpectedDevice;
+ return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
}
}
@@ -10392,6 +10493,9 @@ public class AudioService extends IAudioService.Stub
public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
+
+ final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
final int[] mutedUsages;
synchronized (mMuteAwaitConnectionLock) {
@@ -10401,7 +10505,7 @@ public class AudioService extends IAudioService.Stub
Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
return;
}
- if (!device.equalTypeAddress(mMutingExpectedDevice)) {
+ if (!ada.equalTypeAddress(mMutingExpectedDevice)) {
Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+ "] but expected device is" + mMutingExpectedDevice);
throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
@@ -10411,8 +10515,14 @@ public class AudioService extends IAudioService.Stub
mMutedUsagesAwaitingConnection = null;
mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device);
}
- dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
- AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
+ AudioDeviceAttributes dev = ada;
+ if (!isPrivileged) {
+ dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+ }
+ cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages);
} catch (RemoteException e) { } });
}
@@ -10426,7 +10536,7 @@ public class AudioService extends IAudioService.Stub
super.registerMuteAwaitConnectionDispatcher_enforcePermission();
if (register) {
- mMuteAwaitConnectionDispatchers.register(cb);
+ mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
} else {
mMuteAwaitConnectionDispatchers.unregister(cb);
}
@@ -10450,8 +10560,14 @@ public class AudioService extends IAudioService.Stub
mPlaybackMonitor.cancelMuteAwaitConnection(
"checkMuteAwaitConnection device " + device + " connected, unmuting");
}
- dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
- AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
+ AudioDeviceAttributes ada = device;
+ if (!isPrivileged) {
+ ada = anonymizeAudioDeviceAttributesUnchecked(device);
+ }
+ cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION,
+ ada, mutedUsages);
} catch (RemoteException e) { } });
}
@@ -10471,7 +10587,8 @@ public class AudioService extends IAudioService.Stub
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
}
- dispatchMuteAwaitConnection(cb -> { try {
+ dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+ try {
cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
timedOutDevice, mutedUsages);
@@ -10479,13 +10596,14 @@ public class AudioService extends IAudioService.Stub
}
private void dispatchMuteAwaitConnection(
- java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+ java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) {
final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
// lazy initialization as errors unlikely
ArrayList<IMuteAwaitConnectionCallback> errorList = null;
for (int i = 0; i < nbDispatchers; i++) {
try {
- callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+ callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i),
+ (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i));
} catch (Exception e) {
if (errorList == null) {
errorList = new ArrayList<>(1);
@@ -12951,6 +13069,9 @@ public class AudioService extends IAudioService.Stub
@NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) {
Objects.requireNonNull(device, "device must not be null");
enforceModifyAudioRoutingPermission();
+
+ device = retrieveBluetoothAddress(device);
+
final String getterKey = "additional_output_device_delay="
+ device.getInternalType() + "," + device.getAddress(); // "getter" key as an id.
final String setterKey = getterKey + "," + delayMillis; // append the delay for setter
@@ -12971,6 +13092,9 @@ public class AudioService extends IAudioService.Stub
@IntRange(from = 0)
public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
Objects.requireNonNull(device, "device must not be null");
+
+ device = retrieveBluetoothAddress(device);
+
final String key = "additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
@@ -12998,6 +13122,9 @@ public class AudioService extends IAudioService.Stub
@IntRange(from = 0)
public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
Objects.requireNonNull(device, "device must not be null");
+
+ device = retrieveBluetoothAddress(device);
+
final String key = "max_additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 462c9381b904..969dd60a8012 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.AudioSystem.isBluetoothDevice;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -47,13 +49,13 @@ import android.util.Pair;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
import java.util.UUID;
/**
@@ -73,11 +75,12 @@ public class SpatializerHelper {
private final @NonNull AudioSystemAdapter mASA;
private final @NonNull AudioService mAudioService;
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
private @Nullable SensorManager mSensorManager;
//------------------------------------------------------------
- private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
+ /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
@@ -98,13 +101,6 @@ public class SpatializerHelper {
}
};
- private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
- AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- AudioDeviceInfo.TYPE_BLE_BROADCAST
- };
-
// Spatializer state machine
/*package*/ static final int STATE_UNINITIALIZED = 0;
/*package*/ static final int STATE_NOT_SUPPORTED = 1;
@@ -114,10 +110,15 @@ public class SpatializerHelper {
/*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
private int mState = STATE_UNINITIALIZED;
+ @VisibleForTesting boolean mBinauralEnabledDefault;
+ @VisibleForTesting boolean mTransauralEnabledDefault;
+ @VisibleForTesting boolean mHeadTrackingEnabledDefault;
+
private boolean mFeatureEnabled = false;
/** current level as reported by native Spatializer in callback */
private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+
private boolean mTransauralSupported = false;
private boolean mBinauralSupported = false;
private boolean mIsHeadTrackingSupported = false;
@@ -160,31 +161,21 @@ public class SpatializerHelper {
*/
private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0);
- /**
- * List of devices where Spatial Audio is possible. Each device can be enabled or disabled
- * (== user choice to use or not)
- */
- @GuardedBy("this")
- private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
-
//------------------------------------------------------
// initialization
- @SuppressWarnings("StaticAssignmentInConstructor")
SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
- boolean binauralEnabledDefault,
- boolean transauralEnabledDefault,
- boolean headTrackingEnabledDefault) {
+ @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault,
+ boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) {
mAudioService = mother;
mASA = asa;
- // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
- // constructed here is the factory for SADeviceState, thus SADeviceState and its
- // private static field sHeadTrackingEnabledDefault should never be accessed directly.
- SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
- SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
- SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
+ mDeviceBroker = deviceBroker;
+
+ mBinauralEnabledDefault = binauralEnabledDefault;
+ mTransauralEnabledDefault = transauralEnabledDefault;
+ mHeadTrackingEnabledDefault = headTrackingEnabledDefault;
}
- synchronized void init(boolean effectExpected, @Nullable String settings) {
+ synchronized void init(boolean effectExpected) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -288,10 +279,11 @@ public class SpatializerHelper {
}
}
- // When initialized from AudioService, the settings string will be non-null.
- // Saved settings need to be applied after spatialization support is initialized above.
- if (settings != null) {
- setSADeviceSettings(settings);
+ // Log the saved device states that are compatible with SA
+ for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+ if (isSADevice(deviceState)) {
+ logDeviceState(deviceState, "setSADeviceSettings");
+ }
}
// for both transaural / binaural, we are not forcing enablement as the init() method
@@ -331,7 +323,7 @@ public class SpatializerHelper {
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- init(true, null /* settings */);
+ init(/*effectExpected=*/true);
setSpatializerEnabledInt(featureEnabled);
}
@@ -372,7 +364,7 @@ public class SpatializerHelper {
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
// is media routed to a new device?
- if (isWireless(currentDevice.getType())) {
+ if (isBluetoothDevice(currentDevice.getInternalType())) {
addWirelessDeviceIfNew(currentDevice);
}
@@ -520,8 +512,8 @@ public class SpatializerHelper {
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.mEnabled) {
+ for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+ if (deviceState.isSAEnabled() && isSADevice(deviceState)) {
compatList.add(deviceState.getAudioDeviceAttributes());
}
}
@@ -548,29 +540,48 @@ public class SpatializerHelper {
return;
}
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- SADeviceState deviceUpdated = null; // non-null on update.
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ initSAState(deviceState);
+ AdiDeviceState updatedDevice = null; // non-null on update.
if (deviceState != null) {
- if (forceEnable && !deviceState.mEnabled) {
- deviceUpdated = deviceState;
- deviceUpdated.mEnabled = true;
+ if (forceEnable && !deviceState.isSAEnabled()) {
+ updatedDevice = deviceState;
+ updatedDevice.setSAEnabled(true);
}
} else {
// When adding, force the device type to be a canonical one.
- final int canonicalDeviceType = getCanonicalDeviceType(ada.getType());
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+ ada.getInternalType());
if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
+ ada);
return;
}
- deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
- mSADevices.add(deviceUpdated);
+ updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+ ada.getAddress());
+ initSAState(updatedDevice);
+ mDeviceBroker.addDeviceStateToInventory(updatedDevice);
}
- if (deviceUpdated != null) {
+ if (updatedDevice != null) {
onRoutingUpdated();
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceUpdated, "addCompatibleAudioDevice");
+ mDeviceBroker.persistAudioDeviceSettings();
+ logDeviceState(updatedDevice, "addCompatibleAudioDevice");
+ }
+ }
+
+ private void initSAState(AdiDeviceState device) {
+ if (device == null) {
+ return;
}
+
+ int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
+ Integer.MIN_VALUE);
+ device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+ ? mBinauralEnabledDefault
+ : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+ ? mTransauralEnabledDefault
+ : false);
+ device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
}
private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device.";
@@ -580,29 +591,30 @@ public class SpatializerHelper {
//
// There may be different devices with the same device type (aliasing).
// We always send the full device state info on each change.
- private void logDeviceState(SADeviceState deviceState, String event) {
+ static void logDeviceState(AdiDeviceState deviceState, String event) {
final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
- deviceState.mDeviceType);
+ deviceState.getDeviceType());
final String deviceName = AudioSystem.getDeviceName(deviceType);
new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
- .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
- .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
- .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
- .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
- deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later.
- .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
- deviceState.mHeadTrackerEnabled ? "true" : "false")
- .record();
+ .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress())
+ .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false")
+ .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
+ .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
+ deviceState.hasHeadTracker() ? "true"
+ : "false") // this may be updated later.
+ .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
+ deviceState.isHeadTrackerEnabled() ? "true" : "false")
+ .record();
}
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- if (deviceState != null && deviceState.mEnabled) {
- deviceState.mEnabled = false;
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null && deviceState.isSAEnabled()) {
+ deviceState.setSAEnabled(false);
onRoutingUpdated();
- mAudioService.persistSpatialAudioDeviceSettings();
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "removeCompatibleAudioDevice");
}
}
@@ -611,8 +623,9 @@ public class SpatializerHelper {
* Returns a possibly aliased device type which is used
* for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist).
*/
- private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
- if (isWireless(deviceType)) return deviceType;
+ @AudioDeviceInfo.AudioDeviceType
+ private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) {
+ if (isBluetoothDevice(internalDeviceType)) return deviceType;
final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
@@ -629,18 +642,9 @@ public class SpatializerHelper {
*/
@GuardedBy("this")
@Nullable
- private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
- final int deviceType = ada.getType();
- final boolean isWireless = isWireless(deviceType);
- final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceState.mDeviceType == canonicalDeviceType
- && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
- return deviceState;
- }
- }
- return null;
+ private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada,
+ getCanonicalDeviceType(ada.getType(), ada.getInternalType()));
}
/**
@@ -662,14 +666,14 @@ public class SpatializerHelper {
Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
return new Pair<>(false, false);
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
if (deviceState == null) {
// no matching device state?
Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada);
return new Pair<>(false, false);
}
// found the matching device state.
- return new Pair<>(deviceState.mEnabled, true /* available */);
+ return new Pair<>(deviceState.isSAEnabled(), true /* available */);
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
@@ -678,16 +682,19 @@ public class SpatializerHelper {
}
if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
// wireless device types should be canonical, but we translate to be sure.
- final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+ ada.getInternalType());
if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
+ ada);
return;
}
- final SADeviceState deviceState =
- new SADeviceState(canonicalDeviceType, ada.getAddress());
- mSADevices.add(deviceState);
- mAudioService.persistSpatialAudioDeviceSettings();
+ final AdiDeviceState deviceState =
+ new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+ ada.getAddress());
+ initSAState(deviceState);
+ mDeviceBroker.addDeviceStateToInventory(deviceState);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
}
}
@@ -756,6 +763,12 @@ public class SpatializerHelper {
return false;
}
+ private boolean isSADevice(AdiDeviceState deviceState) {
+ return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
+ deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
+ deviceState.getAudioDeviceAttributes());
+ }
+
synchronized void setFeatureEnabled(boolean enabled) {
loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled);
if (mFeatureEnabled == enabled) {
@@ -768,7 +781,7 @@ public class SpatializerHelper {
return;
}
if (mState == STATE_UNINITIALIZED) {
- init(true, null /* settings */);
+ init(true);
}
setSpatializerEnabledInt(true);
} else {
@@ -1137,16 +1150,16 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
if (deviceState == null) return;
- if (!deviceState.mHasHeadTracker) {
+ if (!deviceState.hasHeadTracker()) {
Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ " device:" + ada + " on a device without headtracker");
return;
}
Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
- deviceState.mHeadTrackerEnabled = enabled;
- mAudioService.persistSpatialAudioDeviceSettings();
+ deviceState.setHeadTrackerEnabled(enabled);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
@@ -1170,8 +1183,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
- return deviceState != null && deviceState.mHasHeadTracker;
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null && deviceState.hasHeadTracker();
}
/**
@@ -1184,14 +1197,14 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
if (deviceState != null) {
- if (!deviceState.mHasHeadTracker) {
- deviceState.mHasHeadTracker = true;
- mAudioService.persistSpatialAudioDeviceSettings();
+ if (!deviceState.hasHeadTracker()) {
+ deviceState.setHasHeadTracker(true);
+ mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "setHasHeadTracker");
}
- return deviceState.mHeadTrackerEnabled;
+ return deviceState.isHeadTrackerEnabled();
}
Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
return false;
@@ -1202,9 +1215,9 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
return deviceState != null
- && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
+ && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled();
}
synchronized boolean isHeadTrackerAvailable() {
@@ -1543,144 +1556,6 @@ public class SpatializerHelper {
pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
+ mTransauralSupported);
pw.println("\tmSpatOutput:" + mSpatOutput);
- pw.println("\tdevices:");
- for (SADeviceState device : mSADevices) {
- pw.println("\t\t" + device);
- }
- }
-
- /*package*/ static final class SADeviceState {
- private static boolean sBinauralEnabledDefault = true;
- private static boolean sTransauralEnabledDefault = true;
- private static boolean sHeadTrackingEnabledDefault = false;
- final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
- final @NonNull String mDeviceAddress;
- boolean mEnabled;
- boolean mHasHeadTracker = false;
- boolean mHeadTrackerEnabled;
- static final String SETTING_FIELD_SEPARATOR = ",";
- static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
- static final String SETTING_DEVICE_SEPARATOR = "\\|";
-
- /**
- * Constructor
- * @param deviceType
- * @param address must be non-null for wireless devices
- * @throws NullPointerException if a null address is passed for a wireless device
- */
- SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
- mDeviceType = deviceType;
- mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
- final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
- ? sBinauralEnabledDefault
- : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
- ? sTransauralEnabledDefault
- : false;
- mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- // type check and cast
- if (getClass() != obj.getClass()) {
- return false;
- }
- final SADeviceState sads = (SADeviceState) obj;
- return mDeviceType == sads.mDeviceType
- && mDeviceAddress.equals(sads.mDeviceAddress)
- && mEnabled == sads.mEnabled
- && mHasHeadTracker == sads.mHasHeadTracker
- && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker,
- mHeadTrackerEnabled);
- }
-
- @Override
- public String toString() {
- return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled
- + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
- }
-
- String toPersistableString() {
- return (new StringBuilder().append(mDeviceType)
- .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
- .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0")
- .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
- .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
- .toString());
- }
-
- static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) {
- if (persistedString == null) {
- return null;
- }
- if (persistedString.isEmpty()) {
- return null;
- }
- String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
- if (fields.length != 5) {
- // expecting all fields, fewer may mean corruption, ignore those settings
- return null;
- }
- try {
- final int deviceType = Integer.parseInt(fields[0]);
- final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]);
- deviceState.mEnabled = Integer.parseInt(fields[2]) == 1;
- deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1;
- deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1;
- return deviceState;
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e);
- return null;
- }
- }
-
- public AudioDeviceAttributes getAudioDeviceAttributes() {
- return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
- }
-
- }
-
- /*package*/ synchronized String getSADeviceSettings() {
- // expected max size of each String for each SADeviceState is 25 (accounting for separator)
- final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25);
- for (int i = 0; i < mSADevices.size(); i++) {
- settingsBuilder.append(mSADevices.get(i).toPersistableString());
- if (i != mSADevices.size() - 1) {
- settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR);
- }
- }
- return settingsBuilder.toString();
- }
-
- /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) {
- String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings),
- SADeviceState.SETTING_DEVICE_SEPARATOR);
- // small list, not worth overhead of Arrays.stream(devSettings)
- for (String setting : devSettings) {
- SADeviceState devState = SADeviceState.fromPersistedString(setting);
- // Note if the device is not compatible with spatialization mode
- // or the device type is not canonical, it is ignored.
- if (devState != null
- && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
- && isDeviceCompatibleWithSpatializationModes(
- devState.getAudioDeviceAttributes())) {
- mSADevices.add(devState);
- logDeviceState(devState, "setSADeviceSettings");
- }
- }
}
private static String spatStateString(int state) {
@@ -1702,15 +1577,6 @@ public class SpatializerHelper {
}
}
- private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) {
- for (int type : WIRELESS_TYPES) {
- if (type == deviceType) {
- return true;
- }
- }
- return false;
- }
-
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
if (sRoutingDevices.isEmpty()) {
@@ -1780,11 +1646,6 @@ public class SpatializerHelper {
//------------------------------------------------
// for testing purposes only
-
- /*package*/ void clearSADevices() {
- mSADevices.clear();
- }
-
/*package*/ synchronized void forceStateForTest(int state) {
mState = state;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 79559fdc34d6..1d92695ca0c4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -9522,14 +9522,19 @@ public class NotificationManagerService extends SystemService {
* Determine whether the userId applies to the notification in question, either because
* they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
*/
- private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
- return
+ private static boolean notificationMatchesUserId(NotificationRecord r, int userId,
+ boolean isAutogroupSummary) {
+ if (isAutogroupSummary) {
+ return r.getUserId() == userId;
+ } else {
+ return
// looking for USER_ALL notifications? match everything
- userId == UserHandle.USER_ALL
- // a notification sent to USER_ALL matches any query
- || r.getUserId() == UserHandle.USER_ALL
- // an exact user match
- || r.getUserId() == userId;
+ userId == UserHandle.USER_ALL
+ // a notification sent to USER_ALL matches any query
+ || r.getUserId() == UserHandle.USER_ALL
+ // an exact user match
+ || r.getUserId() == userId;
+ }
}
/**
@@ -9538,7 +9543,7 @@ public class NotificationManagerService extends SystemService {
* because it matches one of the users profiles.
*/
private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
- return notificationMatchesUserId(r, userId)
+ return notificationMatchesUserId(r, userId, false)
|| mUserProfiles.isCurrentProfile(r.getUserId());
}
@@ -9607,7 +9612,7 @@ public class NotificationManagerService extends SystemService {
if (!notificationMatchesCurrentProfiles(r, userId)) {
continue;
}
- } else if (!notificationMatchesUserId(r, userId)) {
+ } else if (!notificationMatchesUserId(r, userId, false)) {
continue;
}
// Don't remove notifications to all, if there's no package name specified
@@ -9845,7 +9850,7 @@ public class NotificationManagerService extends SystemService {
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+ if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey)
&& r.getSbn().getPackageName().equals(pkg)) {
records.add(r);
}
@@ -9887,8 +9892,8 @@ public class NotificationManagerService extends SystemService {
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
- TextUtils.equals(r.getSbn().getTag(), tag)
+ if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0)
+ && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag)
&& r.getSbn().getPackageName().equals(pkg)) {
return r;
}
@@ -9903,8 +9908,8 @@ public class NotificationManagerService extends SystemService {
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
- TextUtils.equals(r.getSbn().getTag(), tag)
+ if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id
+ && TextUtils.equals(r.getSbn().getTag(), tag)
&& r.getSbn().getPackageName().equals(pkg)) {
matching.add(r);
}
@@ -9937,7 +9942,7 @@ public class NotificationManagerService extends SystemService {
final int n = mEnqueuedNotifications.size();
for (int i = 0; i < n; i++) {
NotificationRecord r = mEnqueuedNotifications.get(i);
- if (notificationMatchesUserId(r, userId)
+ if (notificationMatchesUserId(r, userId, false)
&& r.getSbn().getId() == id
&& TextUtils.equals(r.getSbn().getTag(), tag)
&& r.getSbn().getPackageName().equals(pkg)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b03fb31fbb1e..bea6a2f5c22c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1877,6 +1877,19 @@ public class UserManagerService extends IUserManager.Stub {
return userTypeDetails.getBadgeNoBackground();
}
+ @Override
+ public @StringRes int getProfileLabelResId(@UserIdInt int userId) {
+ checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "getProfileLabelResId");
+ final UserInfo userInfo = getUserInfoNoChecks(userId);
+ final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
+ if (userInfo == null || userTypeDetails == null) {
+ return Resources.ID_NULL;
+ }
+ final int userIndex = userInfo.profileBadge;
+ return userTypeDetails.getLabel(userIndex);
+ }
+
public boolean isProfile(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
return isProfileUnchecked(userId);
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 6065372e1ea0..f97daebd0bea 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -54,8 +54,15 @@ public final class UserTypeDetails {
/** Whether users of this type can be created. */
private final boolean mEnabled;
- // TODO(b/142482943): Currently unused and not set. Hook this up.
- private final int mLabel;
+ /**
+ * Resource IDs ({@link StringRes}) of the user's labels. This might be used to label a
+ * user/profile in tabbed views, etc.
+ * The values are resource IDs referring to the strings not the strings themselves.
+ *
+ * <p>This is an array because, in general, there may be multiple users of the same user type.
+ * In this case, the user is indexed according to its {@link UserInfo#profileBadge}.
+ */
+ private final @Nullable int[] mLabels;
/**
* Maximum number of this user type allowed on the device.
@@ -157,8 +164,8 @@ public final class UserTypeDetails {
private final @NonNull UserProperties mDefaultUserProperties;
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
- @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
- int maxAllowedPerParent,
+ @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags,
+ @Nullable int[] labels, int maxAllowedPerParent,
int iconBadge, int badgePlain, int badgeNoBackground,
@Nullable int[] badgeLabels, @Nullable int[] badgeColors,
@Nullable int[] darkThemeBadgeColors,
@@ -177,11 +184,10 @@ public final class UserTypeDetails {
this.mDefaultSystemSettings = defaultSystemSettings;
this.mDefaultSecureSettings = defaultSecureSettings;
this.mDefaultCrossProfileIntentFilters = defaultCrossProfileIntentFilters;
-
this.mIconBadge = iconBadge;
this.mBadgePlain = badgePlain;
this.mBadgeNoBackground = badgeNoBackground;
- this.mLabel = label;
+ this.mLabels = labels;
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
@@ -229,9 +235,16 @@ public final class UserTypeDetails {
return mDefaultUserInfoPropertyFlags | mBaseType;
}
- // TODO(b/142482943) Hook this up; it is currently unused.
- public int getLabel() {
- return mLabel;
+ /**
+ * Returns the resource ID corresponding to the badgeIndexth label name where the badgeIndex is
+ * expected to be the {@link UserInfo#profileBadge} of the user. If badgeIndex exceeds the
+ * number of labels, returns the label for the highest index.
+ */
+ public @StringRes int getLabel(int badgeIndex) {
+ if (mLabels == null || mLabels.length == 0 || badgeIndex < 0) {
+ return Resources.ID_NULL;
+ }
+ return mLabels[Math.min(badgeIndex, mLabels.length - 1)];
}
/** Returns whether users of this user type should be badged. */
@@ -348,7 +361,6 @@ public final class UserTypeDetails {
pw.print(prefix); pw.print("mMaxAllowedPerParent: "); pw.println(mMaxAllowedPerParent);
pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
- pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
mDefaultUserProperties.println(pw, prefix);
final String restrictionsPrefix = prefix + " ";
@@ -381,6 +393,8 @@ public final class UserTypeDetails {
pw.println(mBadgeColors != null ? mBadgeColors.length : "0(null)");
pw.print(prefix); pw.print("mDarkThemeBadgeColors.length: ");
pw.println(mDarkThemeBadgeColors != null ? mDarkThemeBadgeColors.length : "0(null)");
+ pw.print(prefix); pw.print("mLabels.length: ");
+ pw.println(mLabels != null ? mLabels.length : "0(null)");
}
/** Builder for a {@link UserTypeDetails}; see that class for documentation. */
@@ -397,13 +411,14 @@ public final class UserTypeDetails {
private @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters =
null;
private int mEnabled = 1;
- private int mLabel = Resources.ID_NULL;
+ private @Nullable int[] mLabels = null;
private @Nullable int[] mBadgeLabels = null;
private @Nullable int[] mBadgeColors = null;
private @Nullable int[] mDarkThemeBadgeColors = null;
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+ private @DrawableRes int mStatusBarIcon = Resources.ID_NULL;
// Default UserProperties cannot be null but for efficiency we don't initialize it now.
// If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
private @Nullable UserProperties mDefaultUserProperties = null;
@@ -471,8 +486,9 @@ public final class UserTypeDetails {
return this;
}
- public Builder setLabel(int label) {
- mLabel = label;
+ /** Returns labels */
+ public Builder setLabels(@StringRes int ... labels) {
+ mLabels = labels;
return this;
}
@@ -545,7 +561,7 @@ public final class UserTypeDetails {
mMaxAllowed,
mBaseType,
mDefaultUserInfoPropertyFlags,
- mLabel,
+ mLabels,
mMaxAllowedPerParent,
mIconBadge,
mBadgePlain,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index a814ca46fa5e..c77dc07e2ebc 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -49,6 +49,7 @@ import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
@@ -123,7 +124,7 @@ public final class UserTypeFactory {
.setName(USER_TYPE_PROFILE_CLONE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
- .setLabel(0)
+ .setLabels(R.string.profile_label_clone)
.setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
.setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
// Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
@@ -148,9 +149,15 @@ public final class UserTypeFactory {
UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
.setCrossProfileIntentResolutionStrategy(UserProperties
.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING)
+ .setShowInQuietMode(
+ UserProperties.SHOW_IN_QUIET_MODE_DEFAULT)
+ .setShowInSharingSurfaces(
+ UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT)
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(true)
- .setDeleteAppWithParent(true));
+ .setDeleteAppWithParent(true)
+ .setCrossProfileContentSharingStrategy(UserProperties
+ .CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
}
/**
@@ -163,7 +170,10 @@ public final class UserTypeFactory {
.setBaseType(FLAG_PROFILE)
.setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE)
.setMaxAllowedPerParent(1)
- .setLabel(0)
+ .setLabels(
+ R.string.profile_label_work,
+ R.string.profile_label_work_2,
+ R.string.profile_label_work_3)
.setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case)
.setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background)
@@ -186,6 +196,10 @@ public final class UserTypeFactory {
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setShowInQuietMode(
+ UserProperties.SHOW_IN_QUIET_MODE_PAUSED)
+ .setShowInSharingSurfaces(
+ UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
.setCredentialShareableWithParent(true));
}
@@ -201,7 +215,10 @@ public final class UserTypeFactory {
.setName(USER_TYPE_PROFILE_TEST)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(2)
- .setLabel(0)
+ .setLabels(
+ R.string.profile_label_test,
+ R.string.profile_label_test,
+ R.string.profile_label_test)
.setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
.setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index d0c346a63889..57f4a5ddb2bd 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -337,7 +337,8 @@ public class ArtStatsLogUtils {
0, // deprecated, used to be durationIncludingSleepMs
0, // optimizedPackagesCount
0, // packagesDependingOnBootClasspathCount
- 0); // totalPackagesCount
+ 0, // totalPackagesCount
+ ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__PASS__PASS_UNKNOWN);
}
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f3001133338a..b3ae2ee30f22 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1280,45 +1280,53 @@ final class AccessibilityController {
}
void drawIfNeeded(SurfaceControl.Transaction t) {
+ // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
+ // using WindowManagerGlobalLock. Grab copies of these values before
+ // drawing on the canvas so that drawing can be performed outside of the lock.
+ int alpha;
+ Rect drawingRect = null;
+ Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
if (!mInvalidated) {
return;
}
mInvalidated = false;
- if (mAlpha > 0) {
- Canvas canvas = null;
- try {
- // Empty dirty rectangle means unspecified.
- if (mDirtyRect.isEmpty()) {
- mBounds.getBounds(mDirtyRect);
- }
- mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
- canvas = mSurface.lockCanvas(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
- }
- } catch (IllegalArgumentException iae) {
- /* ignore */
- } catch (Surface.OutOfResourcesException oore) {
- /* ignore */
- }
- if (canvas == null) {
- return;
+
+ alpha = mAlpha;
+ if (alpha > 0) {
+ drawingBounds = new Region(mBounds);
+ // Empty dirty rectangle means unspecified.
+ if (mDirtyRect.isEmpty()) {
+ mBounds.getBounds(mDirtyRect);
}
+ mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
+ drawingRect = new Rect(mDirtyRect);
if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "Bounds: " + mBounds);
+ Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
+ Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
}
- canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
- mPaint.setAlpha(mAlpha);
- Path path = mBounds.getBoundaryPath();
- canvas.drawPath(path, mPaint);
-
- mSurface.unlockCanvasAndPost(canvas);
- t.show(mSurfaceControl);
- } else {
- t.hide(mSurfaceControl);
}
}
+
+ // Draw without holding WindowManagerGlobalLock.
+ if (alpha > 0) {
+ Canvas canvas = null;
+ try {
+ canvas = mSurface.lockCanvas(drawingRect);
+ } catch (IllegalArgumentException | OutOfResourcesException e) {
+ /* ignore */
+ }
+ if (canvas == null) {
+ return;
+ }
+ canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+ mPaint.setAlpha(alpha);
+ canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
+ mSurface.unlockCanvasAndPost(canvas);
+ t.show(mSurfaceControl);
+ } else {
+ t.hide(mSurfaceControl);
+ }
}
void releaseSurface() {
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 01158779c24f..5a7799e318c1 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@ import android.view.animation.AnimationUtils;
import com.android.internal.R;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -236,7 +237,15 @@ class AsyncRotationController extends FadeAnimationController implements Consume
* operation directly to avoid waiting until timeout.
*/
void updateTargetWindows() {
- if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+ if (mTransitionOp == OP_LEGACY) return;
+ if (!mIsStartTransactionCommitted) {
+ if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+ Slog.d(TAG, "Cancel for no change");
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
+ return;
+ }
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final Operation op = mTargetWindowTokens.valueAt(i);
if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -592,6 +601,16 @@ class AsyncRotationController extends FadeAnimationController implements Consume
return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "AsyncRotationController");
+ prefix += " ";
+ pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+ pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+ pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+ pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+ pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
+ }
+
/** The operation to control the rotation appearance associated with window token. */
private static class Operation {
@Retention(RetentionPolicy.SOURCE)
@@ -619,5 +638,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume
Operation(@Action int action) {
mAction = action;
}
+
+ @Override
+ public String toString() {
+ return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef19eef22794..d288663ab2db 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3453,6 +3453,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mFixedRotationLaunchingApp != null) {
setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
}
+ } else if (mAsyncRotationController != null && !isRotationChanging()) {
+ Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+ finishAsyncRotationIfPossible();
}
return;
}
@@ -3621,6 +3624,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mFixedRotationLaunchingApp != null) {
pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
}
+ if (mAsyncRotationController != null) {
+ mAsyncRotationController.dump(pw, prefix);
+ }
pw.println();
pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 65c5c9b35ab7..2ac3125de961 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1468,6 +1468,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mTargetDisplays.add(dc);
}
+ for (int i = 0; i < mTargets.size(); ++i) {
+ final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
+ if (da == null) continue;
+ if (da.isVisibleRequested()) {
+ mController.mValidateDisplayVis.remove(da);
+ } else {
+ // In case something accidentally hides a displayarea and nothing shows it again.
+ mController.mValidateDisplayVis.add(da);
+ }
+ }
+
if (mOverrideOptions != null) {
info.setAnimationOptions(mOverrideOptions);
if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index b05c6b4839e5..7169c2cfa0f6 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -150,6 +150,13 @@ class TransitionController {
final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>();
/**
+ * List of display areas which were last sent as "closing"-type and haven't yet had a
+ * corresponding "opening"-type transition. A mismatch here is usually related to issues in
+ * keyguard unlock.
+ */
+ final ArrayList<DisplayArea> mValidateDisplayVis = new ArrayList<>();
+
+ /**
* Currently playing transitions (in the order they were started). When finished, records are
* removed from this list.
*/
@@ -922,6 +929,15 @@ class TransitionController {
ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y);
}
mValidateActivityCompat.clear();
+ for (int i = 0; i < mValidateDisplayVis.size(); ++i) {
+ final DisplayArea da = mValidateDisplayVis.get(i);
+ if (!da.isAttached() || da.getSurfaceControl() == null) continue;
+ if (da.isVisibleRequested()) {
+ Slog.e(TAG, "DisplayArea became visible outside of a transition: " + da);
+ da.getSyncTransaction().show(da.getSurfaceControl());
+ }
+ }
+ mValidateDisplayVis.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 06978a5338ea..c4506d59c138 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5664,6 +5664,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// TODO(b/233286785): Add sync support to wallpaper.
return false;
}
+ if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+ && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+ && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+ // Skip sync for invisible app windows which are not managed by activity lifecycle.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own