1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.audio; 18 19 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; 20 import static android.media.AudioSystem.DEVICE_NONE; 21 import static android.media.AudioSystem.isBluetoothDevice; 22 import static android.media.audio.Flags.automaticBtDeviceType; 23 24 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.media.AudioDeviceAttributes; 29 import android.media.AudioDeviceInfo; 30 import android.media.AudioManager; 31 import android.media.Utils; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.util.Pair; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.util.Objects; 39 40 /** 41 * Class representing all devices that were previously or are currently connected. Data is 42 * persisted in {@link android.provider.Settings.Secure} 43 */ 44 @VisibleForTesting(visibility = PACKAGE) 45 public final class AdiDeviceState { 46 private static final String TAG = "AS.AdiDeviceState"; 47 48 private static final String SETTING_FIELD_SEPARATOR = ","; 49 50 @AudioDeviceInfo.AudioDeviceType 51 private final int mDeviceType; 52 53 private final int mInternalDeviceType; 54 55 @NonNull 56 private final String mDeviceAddress; 57 58 /** Unique device id from internal device type and address. */ 59 private final Pair<Integer, String> mDeviceId; 60 61 @AudioManager.AudioDeviceCategory 62 private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; 63 64 private boolean mAutoBtCategorySet = false; 65 66 private boolean mSAEnabled; 67 private boolean mHasHeadTracker = false; 68 private boolean mHeadTrackerEnabled; 69 70 /** 71 * Constructor 72 * 73 * @param deviceType external audio device type 74 * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the 75 * default conversion of the external type will be used 76 * @param address must be non-null for wireless devices 77 * @throws NullPointerException if a null address is passed for a wireless device 78 */ AdiDeviceState(@udioDeviceInfo.AudioDeviceType int deviceType, int internalDeviceType, @Nullable String address)79 AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, 80 int internalDeviceType, 81 @Nullable String address) { 82 mDeviceType = deviceType; 83 if (internalDeviceType != DEVICE_NONE) { 84 mInternalDeviceType = internalDeviceType; 85 } else { 86 mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); 87 88 } 89 mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( 90 address) : ""; 91 92 mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); 93 } 94 getDeviceId()95 public synchronized Pair<Integer, String> getDeviceId() { 96 return mDeviceId; 97 } 98 99 @AudioDeviceInfo.AudioDeviceType getDeviceType()100 public synchronized int getDeviceType() { 101 return mDeviceType; 102 } 103 getInternalDeviceType()104 public synchronized int getInternalDeviceType() { 105 return mInternalDeviceType; 106 } 107 108 @NonNull getDeviceAddress()109 public synchronized String getDeviceAddress() { 110 return mDeviceAddress; 111 } 112 setSAEnabled(boolean sAEnabled)113 public synchronized void setSAEnabled(boolean sAEnabled) { 114 mSAEnabled = sAEnabled; 115 } 116 isSAEnabled()117 public synchronized boolean isSAEnabled() { 118 return mSAEnabled; 119 } 120 setHeadTrackerEnabled(boolean headTrackerEnabled)121 public synchronized void setHeadTrackerEnabled(boolean headTrackerEnabled) { 122 mHeadTrackerEnabled = headTrackerEnabled; 123 } 124 isHeadTrackerEnabled()125 public synchronized boolean isHeadTrackerEnabled() { 126 return mHeadTrackerEnabled; 127 } 128 setHasHeadTracker(boolean hasHeadTracker)129 public synchronized void setHasHeadTracker(boolean hasHeadTracker) { 130 mHasHeadTracker = hasHeadTracker; 131 } 132 133 hasHeadTracker()134 public synchronized boolean hasHeadTracker() { 135 return mHasHeadTracker; 136 } 137 138 @AudioDeviceInfo.AudioDeviceType getAudioDeviceCategory()139 public synchronized int getAudioDeviceCategory() { 140 return mAudioDeviceCategory; 141 } 142 setAudioDeviceCategory( @udioDeviceInfo.AudioDeviceType int audioDeviceCategory)143 public synchronized void setAudioDeviceCategory( 144 @AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { 145 mAudioDeviceCategory = audioDeviceCategory; 146 } 147 isBtDeviceCategoryFixed()148 public synchronized boolean isBtDeviceCategoryFixed() { 149 if (!automaticBtDeviceType()) { 150 // do nothing 151 return false; 152 } 153 154 updateAudioDeviceCategory(); 155 return mAutoBtCategorySet; 156 } 157 updateAudioDeviceCategory()158 public synchronized boolean updateAudioDeviceCategory() { 159 if (!automaticBtDeviceType()) { 160 // do nothing 161 return false; 162 } 163 if (!isBluetoothDevice(mInternalDeviceType)) { 164 return false; 165 } 166 if (mAutoBtCategorySet) { 167 // no need to update. The auto value is already set. 168 return false; 169 } 170 171 int newAudioDeviceCategory = BtHelper.getBtDeviceCategory(mDeviceAddress); 172 if (newAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_UNKNOWN) { 173 // no info provided by the BtDevice metadata 174 return false; 175 } 176 177 mAudioDeviceCategory = newAudioDeviceCategory; 178 mAutoBtCategorySet = true; 179 return true; 180 181 } 182 183 @Override equals(Object obj)184 public boolean equals(Object obj) { 185 if (this == obj) { 186 return true; 187 } 188 if (obj == null) { 189 return false; 190 } 191 // type check and cast 192 if (getClass() != obj.getClass()) { 193 return false; 194 } 195 final AdiDeviceState sads = (AdiDeviceState) obj; 196 return mDeviceType == sads.mDeviceType 197 && mInternalDeviceType == sads.mInternalDeviceType 198 && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull 199 && mSAEnabled == sads.mSAEnabled 200 && mHasHeadTracker == sads.mHasHeadTracker 201 && mHeadTrackerEnabled == sads.mHeadTrackerEnabled 202 && mAudioDeviceCategory == sads.mAudioDeviceCategory; 203 } 204 205 @Override hashCode()206 public int hashCode() { 207 return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, 208 mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory); 209 } 210 211 @Override toString()212 public String toString() { 213 return "type: " + mDeviceType 214 + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) 215 + " addr: " + Utils.anonymizeBluetoothAddress(mInternalDeviceType, mDeviceAddress) 216 + " bt audio type: " 217 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) 218 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker 219 + " HTenabled: " + mHeadTrackerEnabled; 220 } 221 toPersistableString()222 public synchronized String toPersistableString() { 223 return (new StringBuilder().append(mDeviceType) 224 .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) 225 .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") 226 .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") 227 .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") 228 .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) 229 .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory) 230 .toString()); 231 } 232 233 /** 234 * Gets the max size (including separators) when persisting the elements with 235 * {@link AdiDeviceState#toPersistableString()}. 236 */ getPeristedMaxSize()237 public static int getPeristedMaxSize() { 238 return 39; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 239 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 240 + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6 241 + (SETTING_DEVICE_SEPARATOR)1 */ 242 } 243 244 @Nullable fromPersistedString(@ullable String persistedString)245 public static AdiDeviceState fromPersistedString(@Nullable String persistedString) { 246 if (persistedString == null) { 247 return null; 248 } 249 if (persistedString.isEmpty()) { 250 return null; 251 } 252 String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); 253 // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal 254 // device type 255 if (fields.length < 5 || fields.length > 7) { 256 // different number of fields may mean corruption, ignore those settings 257 // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory) 258 return null; 259 } 260 try { 261 final int deviceType = Integer.parseInt(fields[0]); 262 int internalDeviceType = -1; 263 if (fields.length >= 6) { 264 internalDeviceType = Integer.parseInt(fields[5]); 265 } 266 int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; 267 if (fields.length == 7) { 268 audioDeviceCategory = Integer.parseInt(fields[6]); 269 } 270 final AdiDeviceState deviceState = new AdiDeviceState(deviceType, 271 internalDeviceType, fields[1]); 272 deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1); 273 deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); 274 deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); 275 deviceState.setAudioDeviceCategory(audioDeviceCategory); 276 // update in case we can automatically determine the category 277 deviceState.updateAudioDeviceCategory(); 278 return deviceState; 279 } catch (NumberFormatException e) { 280 Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); 281 return null; 282 } 283 } 284 getAudioDeviceAttributes()285 public synchronized AudioDeviceAttributes getAudioDeviceAttributes() { 286 return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 287 mDeviceType, mDeviceAddress); 288 } 289 } 290