diff options
18 files changed, 203 insertions, 757 deletions
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index bb8fa9eb4007..6b836adb248c 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -148,7 +148,7 @@ public final class AssociationRequest implements Parcelable { /** @hide */ public void setSkipPrompt(boolean value) { - mSkipPrompt = true; + mSkipPrompt = value; } /** @hide */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1b57aa7e69cb..a1b4deae0989 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9041,13 +9041,6 @@ public final class Settings { public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled"; /** - * Internal collection of audio device inventory items - * The device item stored are {@link com.android.server.audio.AdiDeviceState} - * @hide - */ - public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory"; - - /** * Indicates whether notification display on the lock screen is enabled. * <p> * Type: int (0 for false, 1 for true) diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 993e4e7b4b3d..765901a043a0 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -93,6 +93,9 @@ class ZygoteConnection { throw ex; } + if (peer.getUid() != Process.SYSTEM_UID) { + throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote."); + } isEof = false; } diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java index 9df4bdb157c8..6e33dada9589 100644 --- a/core/java/com/android/internal/util/BinaryXmlSerializer.java +++ b/core/java/com/android/internal/util/BinaryXmlSerializer.java @@ -97,6 +97,8 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer { */ private static final int BUFFER_SIZE = 32_768; + private static final int MAX_UNSIGNED_SHORT = 65_535; + private FastDataOutput mOut; /** @@ -221,6 +223,10 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer { if (namespace != null && !namespace.isEmpty()) throw illegalNamespace(); mOut.writeByte(ATTRIBUTE | TYPE_BYTES_HEX); mOut.writeInternedUTF(name); + if (value.length > MAX_UNSIGNED_SHORT) { + throw new IOException("attributeBytesHex: input size (" + value.length + + ") exceeds maximum allowed size (" + MAX_UNSIGNED_SHORT + ")"); + } mOut.writeShort(value.length); mOut.write(value); return this; @@ -232,6 +238,10 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer { if (namespace != null && !namespace.isEmpty()) throw illegalNamespace(); mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64); mOut.writeInternedUTF(name); + if (value.length > MAX_UNSIGNED_SHORT) { + throw new IOException("attributeBytesBase64: input size (" + value.length + + ") exceeds maximum allowed size (" + MAX_UNSIGNED_SHORT + ")"); + } mOut.writeShort(value.length); mOut.write(value); return this; diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index 248db76da71d..1ad64d58b7c9 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -341,6 +341,18 @@ jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, j return result; } +static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) { + struct ucred credentials; + socklen_t cred_size = sizeof credentials; + if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 + || cred_size != sizeof credentials) { + fail_fn(CREATE_ERROR("Failed to get socket credentials, %s", + strerror(errno))); + } + + return credentials.uid; +} + // Read all lines from the current command into the buffer, and then reset the buffer, so // we will start reading again at the beginning of the command, starting with the argument // count. And we don't need access to the fd to do so. @@ -398,18 +410,12 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z("Failed to retrieve session socket timeout"); } - struct ucred credentials; - socklen_t cred_size = sizeof credentials; - if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 - || cred_size != sizeof credentials) { - fail_fn_1(CREATE_ERROR("ForkMany failed to get initial credentials, %s", strerror(errno))); + uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1); + if (peerUid != static_cast<uid_t>(expected_uid)) { + return JNI_FALSE; } - bool first_time = true; do { - if (credentials.uid != expected_uid) { - return JNI_FALSE; - } n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n); n_buffer->reset(); int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds, @@ -439,30 +445,56 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( // Clear buffer and get count from next command. n_buffer->clear(); for (;;) { + bool valid_session_socket = true; // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */)); if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { if (n_buffer->getCount(fail_fn_z) != 0) { break; - } // else disconnected; + } else { + // Session socket was disconnected + valid_session_socket = false; + close(session_socket); + } } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) { fail_fn_z( CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res)); } - // We've now seen either a disconnect or connect request. - close(session_socket); - int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + int new_fd = -1; + do { + // We've now seen either a disconnect or connect request. + new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + if (new_fd == -1) { + fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + } + uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1); + if (newPeerUid != static_cast<uid_t>(expected_uid)) { + ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid); + close(new_fd); + new_fd = -1; + } else { + // If we still have a valid session socket, close it now + if (valid_session_socket) { + close(session_socket); + } + valid_session_socket = true; + } + } while (!valid_session_socket); + + // At this point we either have a valid new connection (new_fd > 0), or + // an existing session socket we can poll on if (new_fd == -1) { - fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + // The new connection wasn't valid, and we still have an old one; retry polling + continue; } if (new_fd != session_socket) { - // Move new_fd back to the old value, so that we don't have to change Java-level data - // structures to reflect a change. This implicitly closes the old one. - if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { - fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", - new_fd, session_socket, strerror(errno))); - } - close(new_fd); // On Linux, fd is closed even if EINTR is returned. + // Move new_fd back to the old value, so that we don't have to change Java-level data + // structures to reflect a change. This implicitly closes the old one. + if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { + fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", + new_fd, session_socket, strerror(errno))); + } + close(new_fd); // On Linux, fd is closed even if EINTR is returned. } // If we ever return, we effectively reuse the old Java ZygoteConnection. // None of its state needs to change. @@ -474,13 +506,6 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s", session_socket, strerror(errno))); } - if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) { - fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno))); - } - if (cred_size != sizeof credentials) { - fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d", - cred_size, static_cast<int>(sizeof credentials))); - } } first_time = false; } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n)); diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java index fd625dce0254..b369868c0d9a 100644 --- a/core/tests/coretests/src/android/util/BinaryXmlTest.java +++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java @@ -24,6 +24,8 @@ import static android.util.XmlTest.doVerifyRead; import static android.util.XmlTest.doVerifyWrite; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.os.PersistableBundle; @@ -38,12 +40,15 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @RunWith(AndroidJUnit4.class) public class BinaryXmlTest { + private static final int MAX_UNSIGNED_SHORT = 65_535; + /** * Verify that we can write and read large numbers of interned * {@link String} values. @@ -167,4 +172,49 @@ public class BinaryXmlTest { } } } + + @Test + public void testAttributeBytes_BinaryDataOverflow() throws Exception { + final TypedXmlSerializer out = Xml.newBinarySerializer(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + out.setOutput(os, StandardCharsets.UTF_8.name()); + + final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1]; + assertThrows(IOException.class, + () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", + testBytes)); + + assertThrows(IOException.class, + () -> out.attributeBytesBase64(/* namespace */ null, /* name */ + "attributeBytesBase64", testBytes)); + } + + @Test + public void testAttributeBytesHex_MaximumBinaryData() throws Exception { + final TypedXmlSerializer out = Xml.newBinarySerializer(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + out.setOutput(os, StandardCharsets.UTF_8.name()); + + final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT]; + try { + out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes); + } catch (Exception e) { + fail("testAttributeBytesHex fails with exception: " + e.toString()); + } + } + + @Test + public void testAttributeBytesBase64_MaximumBinaryData() throws Exception { + final TypedXmlSerializer out = Xml.newBinarySerializer(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + out.setOutput(os, StandardCharsets.UTF_8.name()); + + final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT]; + try { + out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64", + testBytes); + } catch (Exception e) { + fail("testAttributeBytesBase64 fails with exception: " + e.toString()); + } + } } diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 5341e7e5adc0..7caac899a603 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -64,7 +64,8 @@ public final class AudioDeviceAttributes implements Parcelable { /** * The unique address of the device. Some devices don't have addresses, only an empty string. */ - private @NonNull String mAddress; + private final @NonNull String mAddress; + /** * Is input or output device */ @@ -134,18 +135,6 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide - * Copy Constructor. - * @param ada the copied AudioDeviceAttributes - */ - public AudioDeviceAttributes(AudioDeviceAttributes ada) { - mRole = ada.getRole(); - mType = ada.getType(); - mAddress = ada.getAddress(); - mNativeType = ada.getInternalType(); - } - - /** - * @hide * Returns the role of a device * @return the role */ @@ -176,15 +165,6 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide - * Sets the device address. Only used by audio service. - */ - public void setAddress(@NonNull String address) { - Objects.requireNonNull(address); - mAddress = address; - } - - /** - * @hide * Returns the internal device type of a device * @return the internal device type */ diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 94cf93eafb57..6ff551a68c18 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1120,9 +1120,6 @@ public class AudioSystem public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_USB_SET; - /** @hide */ - public static final Set<Integer> DEVICE_IN_ALL_BLE_SET; - static { DEVICE_IN_ALL_SET = new HashSet<>(); DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION); @@ -1162,66 +1159,6 @@ public class AudioSystem DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); - - DEVICE_IN_ALL_BLE_SET = new HashSet<>(); - DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET); - } - - /** @hide */ - public static boolean isBluetoothDevice(int deviceType) { - return isBluetoothA2dpOutDevice(deviceType) - || isBluetoothScoDevice(deviceType) - || isBluetoothLeDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothOutDevice(int deviceType) { - return isBluetoothA2dpOutDevice(deviceType) - || isBluetoothScoOutDevice(deviceType) - || isBluetoothLeOutDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothInDevice(int deviceType) { - return isBluetoothScoInDevice(deviceType) - || isBluetoothLeInDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothA2dpOutDevice(int deviceType) { - return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoOutDevice(int deviceType) { - return DEVICE_OUT_ALL_SCO_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoInDevice(int deviceType) { - return DEVICE_IN_ALL_SCO_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothScoDevice(int deviceType) { - return isBluetoothScoOutDevice(deviceType) - || isBluetoothScoInDevice(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeOutDevice(int deviceType) { - return DEVICE_OUT_ALL_BLE_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeInDevice(int deviceType) { - return DEVICE_IN_ALL_BLE_SET.contains(deviceType); - } - - /** @hide */ - public static boolean isBluetoothLeDevice(int deviceType) { - return isBluetoothLeOutDevice(deviceType) - || isBluetoothLeInDevice(deviceType); } /** @hide */ diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 229511c26667..9e1a3035a3a7 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -614,7 +614,6 @@ public class SettingsBackupTest { Settings.Secure.ASSIST_SCREENSHOT_ENABLED, Settings.Secure.ASSIST_STRUCTURE_ENABLED, Settings.Secure.ATTENTIVE_TIMEOUT, - Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java deleted file mode 100644 index eab1eca90f6c..000000000000 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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 2c0b6bd9d4fa..b1e62483c472 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -47,7 +47,6 @@ 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; @@ -64,11 +63,8 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -/** - * @hide - * (non final for mocking/spying) - */ -public class AudioDeviceBroker { +/** @hide */ +/*package*/ final class AudioDeviceBroker { private static final String TAG = "AS.AudioDeviceBroker"; @@ -757,8 +753,8 @@ public class AudioDeviceBroker { } /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { - mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { + mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -776,8 +772,8 @@ public class AudioDeviceBroker { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { - mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -785,11 +781,6 @@ public class AudioDeviceBroker { mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); } - /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked( - List<AudioDeviceAttributes> devices) { - return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices); - } - /*package*/ void registerCommunicationDeviceDispatcher( @NonNull ICommunicationDeviceDispatcher dispatcher) { mCommDevDispatchers.register(dispatcher); @@ -1519,9 +1510,6 @@ public class AudioDeviceBroker { final int capturePreset = msg.arg1; mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); } break; - case MSG_PERSIST_AUDIO_DEVICE_SETTINGS: - onPersistAudioDeviceSettings(); - break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1606,8 +1594,6 @@ public class AudioDeviceBroker { private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43; private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44; - 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: @@ -1972,94 +1958,4 @@ public class AudioDeviceBroker { } return null; } - - /** - * 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); - boolean res = Settings.Secure.putStringForUser( - 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 ContentResolver contentResolver = mAudioService.getContentResolver(); - String settings = Settings.Secure.getStringForUser(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 = Settings.Secure.getStringForUser(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 (!Settings.Secure.putStringForUser(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 (!Settings.Secure.putStringForUser(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 4c2b1a6bd7e5..5288a04ce86e 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,8 +15,6 @@ */ package com.android.server.audio; -import static android.media.AudioSystem.isBluetoothDevice; - import android.annotation.NonNull; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; @@ -42,7 +40,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -50,9 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; @@ -66,81 +61,12 @@ 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. - */ - 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") @@ -330,12 +256,6 @@ public class AudioDeviceInventory { mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { pw.println(" " + prefix + "capturePreset:" + capturePreset + " devices:" + devices); }); - pw.println("\ndevices:\n"); - synchronized (mDeviceInventoryLock) { - for (AdiDeviceState device : mDeviceInventory.values()) { - pw.println("\t" + device + "\n"); - } - } } //------------------------------------------------------------ @@ -761,8 +681,8 @@ public class AudioDeviceInventory { } /*package*/ void registerStrategyPreferredDevicesDispatcher( - @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) { - mPrefDevDispatchers.register(dispatcher, isPrivileged); + @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { + mPrefDevDispatchers.register(dispatcher); } /*package*/ void unregisterStrategyPreferredDevicesDispatcher( @@ -796,8 +716,8 @@ public class AudioDeviceInventory { } /*package*/ void registerCapturePresetDevicesRoleDispatcher( - @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) { - mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged); + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDevRoleCapturePresetDispatchers.register(dispatcher); } /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( @@ -852,10 +772,6 @@ public class AudioDeviceInventory { mConnectedDevices.put(deviceKey, new DeviceInfo( device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); - if (AudioSystem.isBluetoothScoDevice(device)) { - addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes( - device, address)); - } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); return true; } else if (!connect && isConnected) { @@ -1074,11 +990,8 @@ public class AudioDeviceInventory { mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); - addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); } - @GuardedBy("mDevicesLock") private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address) @@ -1181,8 +1094,6 @@ public class AudioDeviceInventory { mDeviceBroker.postApplyVolumeOnDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); - addAudioDeviceInInventoryIfNeeded(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address)); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, @@ -1479,9 +1390,6 @@ 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) { @@ -1495,9 +1403,6 @@ 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) { @@ -1506,41 +1411,6 @@ public class AudioDeviceInventory { mDevRoleCapturePresetDispatchers.finishBroadcast(); } - /*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(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 2221d343da0f..2e02e4977094 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2408,11 +2408,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } enforceModifyAudioRoutingPermission(); - - devices = retrieveBluetoothAddresses(devices); - final String logString = String.format( - "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s", + "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, devices.stream().map(e -> e.toString()).collect(Collectors.joining(","))); sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG)); @@ -2460,7 +2457,7 @@ public class AudioService extends IAudioService.Stub status, strategy)); return new ArrayList<AudioDeviceAttributes>(); } else { - return anonymizeAudioDeviceAttributesList(devices); + return devices; } } @@ -2473,8 +2470,7 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerStrategyPreferredDevicesDispatcher( - dispatcher, isBluetoothPrividged()); + mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher); } /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener( @@ -2490,7 +2486,7 @@ public class AudioService extends IAudioService.Stub } /** - * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes) + * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) */ public int setPreferredDevicesForCapturePreset( int capturePreset, List<AudioDeviceAttributes> devices) { @@ -2509,8 +2505,6 @@ public class AudioService extends IAudioService.Stub return AudioSystem.ERROR; } - devices = retrieveBluetoothAddresses(devices); - final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( capturePreset, devices); if (status != AudioSystem.SUCCESS) { @@ -2549,7 +2543,7 @@ public class AudioService extends IAudioService.Stub status, capturePreset)); return new ArrayList<AudioDeviceAttributes>(); } else { - return anonymizeAudioDeviceAttributesList(devices); + return devices; } } @@ -2563,8 +2557,7 @@ public class AudioService extends IAudioService.Stub return; } enforceModifyAudioRoutingPermission(); - mDeviceBroker.registerCapturePresetDevicesRoleDispatcher( - dispatcher, isBluetoothPrividged()); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); } /** @@ -2584,8 +2577,7 @@ public class AudioService extends IAudioService.Stub public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { enforceQueryStateOrModifyRoutingPermission(); - return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList( - getDevicesForAttributesInt(attributes))); + return getDevicesForAttributesInt(attributes); } /** @@ -6166,9 +6158,6 @@ public class AudioService extends IAudioService.Stub // verify arguments Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); - - device = retrieveBluetoothAddress(device); - if (pkgName == null) { pkgName = ""; } @@ -6221,8 +6210,6 @@ public class AudioService extends IAudioService.Stub // verify permissions enforceQueryStateOrModifyRoutingPermission(); - device = retrieveBluetoothAddress(device); - // translate Java device type to native device type (for the devices masks for full / fixed) final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( device.getType()); @@ -6271,9 +6258,6 @@ public class AudioService extends IAudioService.Stub @ConnectionState int state, String address, String name, String caller) { enforceModifyAudioRoutingPermission(); - - address = retrieveBluetoothAddress(type, address); - if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); @@ -8546,117 +8530,6 @@ public class AudioService extends IAudioService.Stub /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0); } - 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; - } - - private String retrieveBluetoothAddress(int type, String address) { - if (isBluetoothPrividged() || !AudioSystem.isBluetoothDevice(type) - || address == null) { - return address; - } - String anonymizedAddress = anonymizeBluetoothAddress(address); - for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) { - if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType()) - && anonymizedAddress.equals(anonymizeBluetoothAddress( - ads.getDeviceAddress())))) { - continue; - } - return ads.getDeviceAddress(); - } - return address; - } - - /** - * 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()); - } - - 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); - } - //========================================================================================== private boolean readCameraSoundForced() { return SystemProperties.getBoolean("audio.camerasound.force", false) || @@ -10781,9 +10654,6 @@ 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 @@ -10804,9 +10674,6 @@ 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()); @@ -10834,9 +10701,6 @@ 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/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 31babe0418b8..93f9e1c2295c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -687,7 +687,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (bp == null) { return; } - if (bp.isDynamic()) { + if (!bp.isDynamic()) { // TODO: switch this back to SecurityException Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + permName); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ee72fc8622a5..b847d2530abb 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -78,6 +78,7 @@ import android.view.RemoteAnimationDefinition; import android.window.SizeConfigurationBuckets; import android.window.TransitionInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; @@ -85,6 +86,9 @@ import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.uri.NeededUriGrants; +import com.android.server.utils.quota.Categorizer; +import com.android.server.utils.quota.Category; +import com.android.server.utils.quota.CountQuotaTracker; import com.android.server.vr.VrManagerInternal; /** @@ -100,6 +104,13 @@ class ActivityClientController extends IActivityClientController.Stub { private final ActivityTaskSupervisor mTaskSupervisor; private final Context mContext; + // Prevent malicious app abusing the Activity#setPictureInPictureParams API + @VisibleForTesting CountQuotaTracker mSetPipAspectRatioQuotaTracker; + // Limit to 60 times / minute + private static final int SET_PIP_ASPECT_RATIO_LIMIT = 60; + // The timeWindowMs here can not be smaller than QuotaTracker#MIN_WINDOW_SIZE_MS + private static final long SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS = 60_000; + /** Wrapper around VoiceInteractionServiceManager. */ private AssistUtils mAssistUtils; @@ -719,6 +730,7 @@ class ActivityClientController extends IActivityClientController.Stub { public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) { final long origId = Binder.clearCallingIdentity(); try { + ensureSetPipAspectRatioQuotaTracker(); synchronized (mGlobalLock) { final ActivityRecord r = ensureValidPictureInPictureActivityParams( "enterPictureInPictureMode", token, params); @@ -733,6 +745,7 @@ class ActivityClientController extends IActivityClientController.Stub { public void setPictureInPictureParams(IBinder token, final PictureInPictureParams params) { final long origId = Binder.clearCallingIdentity(); try { + ensureSetPipAspectRatioQuotaTracker(); synchronized (mGlobalLock) { final ActivityRecord r = ensureValidPictureInPictureActivityParams( "setPictureInPictureParams", token, params); @@ -767,6 +780,19 @@ class ActivityClientController extends IActivityClientController.Stub { } /** + * Initialize the {@link #mSetPipAspectRatioQuotaTracker} if applicable, which should happen + * out of {@link #mGlobalLock} to avoid deadlock (AM lock is used in QuotaTrack ctor). + */ + private void ensureSetPipAspectRatioQuotaTracker() { + if (mSetPipAspectRatioQuotaTracker == null) { + mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext, + Categorizer.SINGLE_CATEGORIZER); + mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, + SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS); + } + } + + /** * Checks the state of the system and the activity associated with the given {@param token} to * verify that picture-in-picture is supported for that activity. * @@ -790,6 +816,19 @@ class ActivityClientController extends IActivityClientController.Stub { + ": Current activity does not support picture-in-picture."); } + // Rate limit how frequent an app can request aspect ratio change via + // Activity#setPictureInPictureParams + final int userId = UserHandle.getCallingUserId(); + if (r.pictureInPictureArgs.hasSetAspectRatio() + && params.hasSetAspectRatio() + && !r.pictureInPictureArgs.getAspectRatioRational().equals( + params.getAspectRatioRational()) + && !mSetPipAspectRatioQuotaTracker.noteEvent( + userId, r.packageName, "setPipAspectRatio")) { + throw new IllegalStateException(caller + + ": Too many PiP aspect ratio change requests from " + r.packageName); + } + if (params.hasSetAspectRatio() && !mService.mWindowManager.isValidPictureInPictureAspectRatio( r.mDisplayContent, params.getAspectRatio())) { diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index d33ff03df02a..5c53d43fa1df 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -29,7 +29,6 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; -import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.util.Log; @@ -200,37 +199,6 @@ public class AudioDeviceBrokerTest { any(Intent.class)); } - /** - * Test that constructing an AdiDeviceState instance requires a non-null address for a - * wireless type, but can take null for a non-wireless type; - * @throws Exception - */ - @Test - public void testAdiDeviceStateNullAddressCtor() throws Exception { - try { - new AdiDeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - AudioManager.DEVICE_OUT_SPEAKER, null); - new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null); - Assert.fail(); - } catch (NullPointerException e) { } - } - - @Test - public void testAdiDeviceStateStringSerialization() throws Exception { - Log.i(TAG, "starting testAdiDeviceStateStringSerialization"); - final AdiDeviceState devState = new AdiDeviceState( - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioManager.DEVICE_OUT_SPEAKER, "bla"); - devState.setHasHeadTracker(false); - devState.setHeadTrackerEnabled(false); - devState.setSAEnabled(true); - final String persistString = devState.toPersistableString(); - final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString); - Log.i(TAG, "original:" + devState); - Log.i(TAG, "result :" + result); - Assert.assertEquals(devState, result); - } - private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection, boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception { when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC)) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 75a87ba9e04d..89ffb2b4bfc7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -881,6 +881,12 @@ public class WindowOrganizerTests extends WindowTestsBase { assertNotNull(o.mInfo); assertNotNull(o.mInfo.pictureInPictureParams); + // Bypass the quota check, which causes NPE in current test setup. + if (mWm.mAtmService.mActivityClientController.mSetPipAspectRatioQuotaTracker != null) { + mWm.mAtmService.mActivityClientController.mSetPipAspectRatioQuotaTracker + .setEnabled(false); + } + final PictureInPictureParams p2 = new PictureInPictureParams.Builder() .setAspectRatio(new Rational(3, 4)).build(); mWm.mAtmService.mActivityClientController.setPictureInPictureParams(record.token, p2); diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index f63866054064..32bb643b8763 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; + import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import android.annotation.NonNull; @@ -42,6 +44,7 @@ import android.os.AsyncTask; import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.usb.UsbProfileGroupSettingsManagerProto; import android.service.usb.UsbSettingsAccessoryPreferenceProto; import android.service.usb.UsbSettingsDevicePreferenceProto; @@ -908,10 +911,28 @@ class UsbProfileGroupSettingsManager { return; } + if (shouldRestrictOverlayActivities()) { + return; + } + // Start activity with registered intent resolveActivity(intent, matches, defaultActivity, device, null); } + private boolean shouldRestrictOverlayActivities() { + if (Settings.Secure.getIntForUser( + mContext.getContentResolver(), + USER_SETUP_COMPLETE, + /* defaultValue= */ 1, + UserHandle.CURRENT.getIdentifier()) + == 0) { + Slog.d(TAG, "restricting usb overlay activities as setup is not complete"); + return true; + } + + return false; + } + public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) { final Intent intent = createDeviceAttachedIntent(device); |