• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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