diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 565e29561d190..65be5f06d08f8 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -59,6 +59,7 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -1794,4 +1795,10 @@ import java.util.concurrent.atomic.AtomicBoolean; } return null; } + + UUID getDeviceSensorUuid(AudioDeviceAttributes device) { + synchronized (mDeviceStateLock) { + return mDeviceInventory.getDeviceSensorUuid(device); + } + } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 2dd6bf5755794..f5a529b4e7548 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -48,7 +48,9 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.UUID; /** * Class to manage the inventory of all connected devices. @@ -185,12 +187,20 @@ public class AudioDeviceInventory { final @NonNull String mDeviceName; final @NonNull String mDeviceAddress; int mDeviceCodecFormat; + final UUID mSensorUuid; - DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { + DeviceInfo(int deviceType, String deviceName, String deviceAddress, + int deviceCodecFormat, UUID sensorUuid) { mDeviceType = deviceType; mDeviceName = deviceName == null ? "" : deviceName; mDeviceAddress = deviceAddress == null ? "" : deviceAddress; mDeviceCodecFormat = deviceCodecFormat; + mSensorUuid = sensorUuid; + } + + DeviceInfo(int deviceType, String deviceName, String deviceAddress, + int deviceCodecFormat) { + this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); } @Override @@ -199,7 +209,8 @@ public class AudioDeviceInventory { + " (" + AudioSystem.getDeviceName(mDeviceType) + ") name:" + mDeviceName + " addr:" + mDeviceAddress - + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; + + " codec: " + Integer.toHexString(mDeviceCodecFormat) + + " sensorUuid: " + Objects.toString(mSensorUuid) + "]"; } @NonNull String getKey() { @@ -980,8 +991,13 @@ public class AudioDeviceInventory { // Reset A2DP suspend state each time a new sink is connected mAudioSystem.setParameters("A2dpSuspended=false"); + // The convention for head tracking sensors associated with A2DP devices is to + // use a UUID derived from the MAC address as follows: + // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, a2dpCodec); + address, a2dpCodec, sensorUuid); final String diKey = di.getKey(); mConnectedDevices.put(diKey, di); // on a connection always overwrite the device seen by AudioPolicy, see comment above when @@ -1456,6 +1472,17 @@ public class AudioDeviceInventory { mDevRoleCapturePresetDispatchers.finishBroadcast(); } + UUID getDeviceSensorUuid(AudioDeviceAttributes device) { + final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), + device.getAddress()); + synchronized (mDevicesLock) { + DeviceInfo di = mConnectedDevices.get(key); + if (di == null) { + return null; + } + return di.mSensorUuid; + } + } //---------------------------------------------------------- // 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 0ea936e35a374..a853ac599a7dc 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -190,6 +190,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -11017,6 +11018,10 @@ public class AudioService extends IAudioService.Stub return delayMillis; } + UUID getDeviceSensorUuid(AudioDeviceAttributes device) { + return mDeviceBroker.getDeviceSensorUuid(device); + } + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index e6789d5e1d492..106cbba913d62 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -43,6 +43,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.UUID; /** * A helper class to manage Spatializer related functionality @@ -168,6 +169,7 @@ public class SpatializerHelper { return; } mState = STATE_DISABLED_UNAVAILABLE; + mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); // note at this point mSpat is still not instantiated } @@ -204,6 +206,11 @@ public class SpatializerHelper { logd("onRoutingUpdated: can spatialize media 5.1:" + able + " on device:" + ROUTING_DEVICES[0]); setDispatchAvailableState(able); + + if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED + && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { + postInitSensors(); + } } //------------------------------------------------------ @@ -909,12 +916,21 @@ public class SpatializerHelper { } } // initialize sensor handles - // TODO-HT update to non-private sensor once head tracker sensor is defined - for (Sensor sensor : mSensorManager.getDynamicSensorList( - Sensor.TYPE_DEVICE_PRIVATE_BASE)) { - if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) { - headHandle = sensor.getHandle(); - break; + UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]); + List sensors = new ArrayList(0); + sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER)); + sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE)); + for (Sensor sensor : sensors) { + if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER + || sensor.getStringType().equals(HEADTRACKER_SENSOR)) { + UUID uuid = sensor.getUuid(); + if (uuid.equals(routingDeviceUuid)) { + headHandle = sensor.getHandle(); + break; + } + if (uuid.equals(UuidUtils.STANDALONE_UUID)) { + headHandle = sensor.getHandle(); + } } } Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java new file mode 100644 index 0000000000000..2003619b583ce --- /dev/null +++ b/services/core/java/com/android/server/audio/UuidUtils.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import android.media.AudioDeviceAttributes; +import android.media.AudioSystem; +import android.util.Slog; + +import java.util.UUID; + +/** + * UuidUtils class implements helper functions to handle unique identifiers + * used to associate head tracking sensors to audio devices. + */ +class UuidUtils { + private static final String TAG = "AudioService.UuidUtils"; + + private static final long LSB_PREFIX_MASK = 0xFFFF000000000000L; + private static final long LSB_SUFFIX_MASK = 0x0000FFFFFFFFFFFFL; + // The sensor UUID for Bluetooth devices is defined as follows: + // - 8 most significant bytes: All 0s + // - 8 most significant bytes: Ascii B, Ascii T, Device MAC address on 6 bytes + private static final long LSB_PREFIX_BT = 0x4254000000000000L; + + /** + * Special UUID for a head tracking sensor not associated with an audio device. + */ + public static final UUID STANDALONE_UUID = new UUID(0, 0); + + /** + * Generate a headtracking UUID from AudioDeviceAttributes + */ + public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) { + switch (device.getInternalType()) { + case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP: + String address = device.getAddress().replace(":", ""); + if (address.length() != 12) { + return null; + } + address = "0x" + address; + long lsb = LSB_PREFIX_BT; + try { + lsb |= Long.decode(address).longValue(); + } catch (NumberFormatException e) { + return null; + } + if (AudioService.DEBUG_DEVICES) { + Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb)); + } + return new UUID(0, lsb); + default: + // Handle other device types here + return null; + } + } +}