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 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.media.AudioDeviceAttributes; 26 import android.media.AudioDeviceInfo; 27 import android.media.AudioManager; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.util.Pair; 31 32 import java.util.Objects; 33 34 /** 35 * Class representing all devices that were previously or are currently connected. Data is 36 * persisted in {@link android.provider.Settings.Secure} 37 */ 38 /*package*/ final class AdiDeviceState { 39 private static final String TAG = "AS.AdiDeviceState"; 40 41 private static final String SETTING_FIELD_SEPARATOR = ","; 42 43 @AudioDeviceInfo.AudioDeviceType 44 private final int mDeviceType; 45 46 private final int mInternalDeviceType; 47 48 @NonNull 49 private final String mDeviceAddress; 50 51 /** Unique device id from internal device type and address. */ 52 private final Pair<Integer, String> mDeviceId; 53 54 @AudioManager.AudioDeviceCategory 55 private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; 56 57 private boolean mSAEnabled; 58 private boolean mHasHeadTracker = false; 59 private boolean mHeadTrackerEnabled; 60 61 /** 62 * Constructor 63 * 64 * @param deviceType external audio device type 65 * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the 66 * default conversion of the external type will be used 67 * @param address must be non-null for wireless devices 68 * @throws NullPointerException if a null address is passed for a wireless device 69 */ AdiDeviceState(@udioDeviceInfo.AudioDeviceType int deviceType, int internalDeviceType, @Nullable String address)70 AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, 71 int internalDeviceType, 72 @Nullable String address) { 73 mDeviceType = deviceType; 74 if (internalDeviceType != DEVICE_NONE) { 75 mInternalDeviceType = internalDeviceType; 76 } else { 77 mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); 78 79 } 80 mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( 81 address) : ""; 82 83 mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); 84 } 85 getDeviceId()86 public Pair<Integer, String> getDeviceId() { 87 return mDeviceId; 88 } 89 90 @AudioDeviceInfo.AudioDeviceType getDeviceType()91 public int getDeviceType() { 92 return mDeviceType; 93 } 94 getInternalDeviceType()95 public int getInternalDeviceType() { 96 return mInternalDeviceType; 97 } 98 99 @NonNull getDeviceAddress()100 public String getDeviceAddress() { 101 return mDeviceAddress; 102 } 103 setSAEnabled(boolean sAEnabled)104 public void setSAEnabled(boolean sAEnabled) { 105 mSAEnabled = sAEnabled; 106 } 107 isSAEnabled()108 public boolean isSAEnabled() { 109 return mSAEnabled; 110 } 111 setHeadTrackerEnabled(boolean headTrackerEnabled)112 public void setHeadTrackerEnabled(boolean headTrackerEnabled) { 113 mHeadTrackerEnabled = headTrackerEnabled; 114 } 115 isHeadTrackerEnabled()116 public boolean isHeadTrackerEnabled() { 117 return mHeadTrackerEnabled; 118 } 119 setHasHeadTracker(boolean hasHeadTracker)120 public void setHasHeadTracker(boolean hasHeadTracker) { 121 mHasHeadTracker = hasHeadTracker; 122 } 123 124 hasHeadTracker()125 public boolean hasHeadTracker() { 126 return mHasHeadTracker; 127 } 128 129 @AudioDeviceInfo.AudioDeviceType getAudioDeviceCategory()130 public int getAudioDeviceCategory() { 131 return mAudioDeviceCategory; 132 } 133 setAudioDeviceCategory(@udioDeviceInfo.AudioDeviceType int audioDeviceCategory)134 public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { 135 mAudioDeviceCategory = audioDeviceCategory; 136 } 137 138 @Override equals(Object obj)139 public boolean equals(Object obj) { 140 if (this == obj) { 141 return true; 142 } 143 if (obj == null) { 144 return false; 145 } 146 // type check and cast 147 if (getClass() != obj.getClass()) { 148 return false; 149 } 150 final AdiDeviceState sads = (AdiDeviceState) obj; 151 return mDeviceType == sads.mDeviceType 152 && mInternalDeviceType == sads.mInternalDeviceType 153 && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull 154 && mSAEnabled == sads.mSAEnabled 155 && mHasHeadTracker == sads.mHasHeadTracker 156 && mHeadTrackerEnabled == sads.mHeadTrackerEnabled 157 && mAudioDeviceCategory == sads.mAudioDeviceCategory; 158 } 159 160 @Override hashCode()161 public int hashCode() { 162 return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, 163 mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory); 164 } 165 166 @Override toString()167 public String toString() { 168 return "type: " + mDeviceType 169 + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) 170 + " addr: " + mDeviceAddress + " bt audio type: " 171 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) 172 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker 173 + " HTenabled: " + mHeadTrackerEnabled; 174 } 175 toPersistableString()176 public String toPersistableString() { 177 return (new StringBuilder().append(mDeviceType) 178 .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) 179 .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") 180 .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") 181 .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") 182 .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) 183 .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory) 184 .toString()); 185 } 186 187 /** 188 * Gets the max size (including separators) when persisting the elements with 189 * {@link AdiDeviceState#toPersistableString()}. 190 */ getPeristedMaxSize()191 public static int getPeristedMaxSize() { 192 return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1 193 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 194 + (SETTINGS_FIELD_SEPARATOR)5 */ 195 } 196 197 @Nullable fromPersistedString(@ullable String persistedString)198 public static AdiDeviceState fromPersistedString(@Nullable String persistedString) { 199 if (persistedString == null) { 200 return null; 201 } 202 if (persistedString.isEmpty()) { 203 return null; 204 } 205 String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); 206 // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal 207 // device type 208 if (fields.length < 5 || fields.length > 7) { 209 // different number of fields may mean corruption, ignore those settings 210 // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory) 211 return null; 212 } 213 try { 214 final int deviceType = Integer.parseInt(fields[0]); 215 int internalDeviceType = -1; 216 if (fields.length >= 6) { 217 internalDeviceType = Integer.parseInt(fields[5]); 218 } 219 int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; 220 if (fields.length == 7) { 221 audioDeviceCategory = Integer.parseInt(fields[6]); 222 } 223 final AdiDeviceState deviceState = new AdiDeviceState(deviceType, 224 internalDeviceType, fields[1]); 225 deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1); 226 deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); 227 deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); 228 deviceState.setAudioDeviceCategory(audioDeviceCategory); 229 return deviceState; 230 } catch (NumberFormatException e) { 231 Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); 232 return null; 233 } 234 } 235 getAudioDeviceAttributes()236 public AudioDeviceAttributes getAudioDeviceAttributes() { 237 return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 238 mDeviceType, mDeviceAddress); 239 } 240 } 241