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 + "internal type: " + mInternalDeviceType 169 + " addr: " + mDeviceAddress + " bt audio type: " 170 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) 171 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker 172 + " HTenabled: " + mHeadTrackerEnabled; 173 } 174 toPersistableString()175 public String toPersistableString() { 176 return (new StringBuilder().append(mDeviceType) 177 .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) 178 .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") 179 .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") 180 .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") 181 .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) 182 .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory) 183 .toString()); 184 } 185 186 /** 187 * Gets the max size (including separators) when persisting the elements with 188 * {@link AdiDeviceState#toPersistableString()}. 189 */ getPeristedMaxSize()190 public static int getPeristedMaxSize() { 191 return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1 192 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 193 + (SETTINGS_FIELD_SEPARATOR)5 */ 194 } 195 196 @Nullable fromPersistedString(@ullable String persistedString)197 public static AdiDeviceState fromPersistedString(@Nullable String persistedString) { 198 if (persistedString == null) { 199 return null; 200 } 201 if (persistedString.isEmpty()) { 202 return null; 203 } 204 String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); 205 // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal 206 // device type 207 if (fields.length < 5 || fields.length > 7) { 208 // different number of fields may mean corruption, ignore those settings 209 // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory) 210 return null; 211 } 212 try { 213 final int deviceType = Integer.parseInt(fields[0]); 214 int internalDeviceType = -1; 215 if (fields.length >= 6) { 216 internalDeviceType = Integer.parseInt(fields[5]); 217 } 218 int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; 219 if (fields.length == 7) { 220 audioDeviceCategory = Integer.parseInt(fields[6]); 221 } 222 final AdiDeviceState deviceState = new AdiDeviceState(deviceType, 223 internalDeviceType, fields[1]); 224 deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1); 225 deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); 226 deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); 227 deviceState.setAudioDeviceCategory(audioDeviceCategory); 228 return deviceState; 229 } catch (NumberFormatException e) { 230 Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); 231 return null; 232 } 233 } 234 getAudioDeviceAttributes()235 public AudioDeviceAttributes getAudioDeviceAttributes() { 236 return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 237 mDeviceType, mDeviceAddress); 238 } 239 } 240