1 /*
2  * Copyright (C) 2019 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 android.media;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * @hide
35  * Class to represent the attributes of an audio device: its type (speaker, headset...), address
36  * (if known) and role (input, output).
37  * <p>Unlike {@link AudioDeviceInfo}, the device
38  * doesn't need to be connected to be uniquely identified, it can
39  * for instance represent a specific A2DP headset even after a
40  * disconnection, whereas the corresponding <code>AudioDeviceInfo</code>
41  * would then be invalid.
42  * <p>While creating / obtaining an instance is not protected by a
43  * permission, APIs using one rely on MODIFY_AUDIO_ROUTING.
44  */
45 @SystemApi
46 public final class AudioDeviceAttributes implements Parcelable {
47 
48     /**
49      * A role identifying input devices, such as microphones.
50      */
51     public static final int ROLE_INPUT = AudioPort.ROLE_SOURCE;
52     /**
53      * A role identifying output devices, such as speakers or headphones.
54      */
55     public static final int ROLE_OUTPUT = AudioPort.ROLE_SINK;
56 
57     /** @hide */
58     @IntDef(flag = false, prefix = "ROLE_", value = {
59             ROLE_INPUT, ROLE_OUTPUT }
60     )
61     @Retention(RetentionPolicy.SOURCE)
62     public @interface Role {}
63 
64     /**
65      * The audio device type, as defined in {@link AudioDeviceInfo}
66      */
67     private final @AudioDeviceInfo.AudioDeviceType int mType;
68     /**
69      * The unique address of the device. Some devices don't have addresses, only an empty string.
70      */
71     private @NonNull String mAddress;
72     /**
73      * The non-unique name of the device. Some devices don't have names, only an empty string.
74      * Should not be used as a unique identifier for a device.
75      */
76     private final @NonNull String mName;
77     /**
78      * Is input or output device
79      */
80     private final @Role int mRole;
81     /**
82      * The internal audio device type
83      */
84     private final int mNativeType;
85     /**
86      * List of AudioProfiles supported by the device
87      */
88     private final @NonNull List<AudioProfile> mAudioProfiles;
89     /**
90      * List of AudioDescriptors supported by the device
91      */
92     private final @NonNull List<AudioDescriptor> mAudioDescriptors;
93 
94     /**
95      * @hide
96      * Constructor from a valid {@link AudioDeviceInfo}
97      * @param deviceInfo the connected audio device from which to obtain the device-identifying
98      *                   type and address.
99      */
100     @SystemApi
AudioDeviceAttributes(@onNull AudioDeviceInfo deviceInfo)101     public AudioDeviceAttributes(@NonNull AudioDeviceInfo deviceInfo) {
102         Objects.requireNonNull(deviceInfo);
103         mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
104         mType = deviceInfo.getType();
105         mAddress = deviceInfo.getAddress();
106         mName = String.valueOf(deviceInfo.getProductName());
107         mNativeType = deviceInfo.getInternalType();
108         mAudioProfiles = deviceInfo.getAudioProfiles();
109         mAudioDescriptors = deviceInfo.getAudioDescriptors();
110     }
111 
112     /**
113      * @hide
114      * Constructor from role, device type and address
115      * @param role indicates input or output role
116      * @param type the device type, as defined in {@link AudioDeviceInfo}
117      * @param address the address of the device, or an empty string for devices without one
118      */
119     @SystemApi
AudioDeviceAttributes(@ole int role, @AudioDeviceInfo.AudioDeviceType int type, @NonNull String address)120     public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
121             @NonNull String address) {
122         this(role, type, address, "", new ArrayList<>(), new ArrayList<>());
123     }
124 
125     /**
126      * @hide
127      * Constructor with specification of all attributes
128      * @param role indicates input or output role
129      * @param type the device type, as defined in {@link AudioDeviceInfo}
130      * @param address the address of the device, or an empty string for devices without one
131      * @param name the name of the device, or an empty string for devices without one
132      * @param profiles the list of AudioProfiles supported by the device
133      * @param descriptors the list of AudioDescriptors supported by the device
134      */
135     @SystemApi
AudioDeviceAttributes(@ole int role, @AudioDeviceInfo.AudioDeviceType int type, @NonNull String address, @NonNull String name, @NonNull List<AudioProfile> profiles, @NonNull List<AudioDescriptor> descriptors)136     public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
137             @NonNull String address, @NonNull String name, @NonNull List<AudioProfile> profiles,
138             @NonNull List<AudioDescriptor> descriptors) {
139         Objects.requireNonNull(address);
140         if (role != ROLE_OUTPUT && role != ROLE_INPUT) {
141             throw new IllegalArgumentException("Invalid role " + role);
142         }
143         if (role == ROLE_OUTPUT) {
144             AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
145             mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(type);
146         } else if (role == ROLE_INPUT) {
147             AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
148             mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type, address);
149         } else {
150             mNativeType = AudioSystem.DEVICE_NONE;
151         }
152 
153         mRole = role;
154         mType = type;
155         mAddress = address;
156         mName = name;
157         mAudioProfiles = profiles;
158         mAudioDescriptors = descriptors;
159     }
160 
161     /**
162      * @hide
163      * Constructor called from AudioSystem JNI when creating an AudioDeviceAttributes from a native
164      * AudioDeviceTypeAddr instance.
165      * @param nativeType the internal device type, as defined in {@link AudioSystem}
166      * @param address the address of the device, or an empty string for devices without one
167      */
AudioDeviceAttributes(int nativeType, @NonNull String address)168     public AudioDeviceAttributes(int nativeType, @NonNull String address) {
169         this(nativeType, address, "");
170     }
171 
172     /**
173      * @hide
174      * Constructor called from BtHelper to connect or disconnect a Bluetooth device.
175      * @param nativeType the internal device type, as defined in {@link AudioSystem}
176      * @param address the address of the device, or an empty string for devices without one
177      * @param name the name of the device, or an empty string for devices without one
178      */
AudioDeviceAttributes(int nativeType, @NonNull String address, @NonNull String name)179     public AudioDeviceAttributes(int nativeType, @NonNull String address, @NonNull String name) {
180         mRole = AudioSystem.isInputDevice(nativeType) ? ROLE_INPUT : ROLE_OUTPUT;
181         mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
182         mAddress = address;
183         mName = name;
184         mNativeType = nativeType;
185         mAudioProfiles = new ArrayList<>();
186         mAudioDescriptors = new ArrayList<>();
187     }
188 
189     /**
190      * @hide
191      * Copy Constructor.
192      * @param ada the copied AudioDeviceAttributes
193      */
AudioDeviceAttributes(AudioDeviceAttributes ada)194     public AudioDeviceAttributes(AudioDeviceAttributes ada) {
195         mRole = ada.getRole();
196         mType = ada.getType();
197         mAddress = ada.getAddress();
198         mName = ada.getName();
199         mNativeType = ada.getInternalType();
200         mAudioProfiles = ada.getAudioProfiles();
201         mAudioDescriptors = ada.getAudioDescriptors();
202     }
203 
204     /**
205      * @hide
206      * Returns the role of a device
207      * @return the role
208      */
209     @SystemApi
getRole()210     public @Role int getRole() {
211         return mRole;
212     }
213 
214     /**
215      * @hide
216      * Returns the audio device type of a device
217      * @return the type, as defined in {@link AudioDeviceInfo}
218      */
219     @SystemApi
getType()220     public @AudioDeviceInfo.AudioDeviceType int getType() {
221         return mType;
222     }
223 
224     /**
225      * @hide
226      * Returns the address of the audio device, or an empty string for devices without one
227      * @return the device address
228      */
229     @SystemApi
getAddress()230     public @NonNull String getAddress() {
231         return mAddress;
232     }
233 
234     /**
235      * @hide
236      * Sets the device address. Only used by audio service.
237      */
setAddress(@onNull String address)238     public void setAddress(@NonNull String address) {
239         Objects.requireNonNull(address);
240         mAddress = address;
241     }
242 
243     /**
244      * @hide
245      * Returns the name of the audio device, or an empty string for devices without one
246      * @return the device name
247      */
248     @SystemApi
getName()249     public @NonNull String getName() {
250         return mName;
251     }
252 
253     /**
254      * @hide
255      * Returns the internal device type of a device
256      * @return the internal device type
257      */
getInternalType()258     public int getInternalType() {
259         return mNativeType;
260     }
261 
262     /**
263      * @hide
264      * Returns the list of AudioProfiles supported by the device
265      * @return the list of AudioProfiles
266      */
267     @SystemApi
getAudioProfiles()268     public @NonNull List<AudioProfile> getAudioProfiles() {
269         return mAudioProfiles;
270     }
271 
272     /**
273      * @hide
274      * Returns the list of AudioDescriptors supported by the device
275      * @return the list of AudioDescriptors
276      */
277     @SystemApi
getAudioDescriptors()278     public @NonNull List<AudioDescriptor> getAudioDescriptors() {
279         return mAudioDescriptors;
280     }
281 
282     @Override
hashCode()283     public int hashCode() {
284         return Objects.hash(mRole, mType, mAddress, mName, mAudioProfiles, mAudioDescriptors);
285     }
286 
287     @Override
equals(Object o)288     public boolean equals(Object o) {
289         if (this == o) return true;
290         if (o == null || getClass() != o.getClass()) return false;
291 
292         AudioDeviceAttributes that = (AudioDeviceAttributes) o;
293         return ((mRole == that.mRole)
294                 && (mType == that.mType)
295                 && mAddress.equals(that.mAddress)
296                 && mName.equals(that.mName)
297                 && mAudioProfiles.equals(that.mAudioProfiles)
298                 && mAudioDescriptors.equals(that.mAudioDescriptors));
299     }
300 
301     /**
302      * Returns true if the role, type and address are equal. Called to compare with an
303      * AudioDeviceAttributes that was created from a native AudioDeviceTypeAddr instance.
304      * @param o object to compare with
305      * @return whether role, type and address are equal
306      */
equalTypeAddress(@ullable Object o)307     public boolean equalTypeAddress(@Nullable Object o) {
308         if (this == o) return true;
309         if (o == null || getClass() != o.getClass()) return false;
310 
311         AudioDeviceAttributes that = (AudioDeviceAttributes) o;
312         return ((mRole == that.mRole)
313                 && (mType == that.mType)
314                 && mAddress.equals(that.mAddress));
315     }
316 
317     /** @hide */
roleToString(@ole int role)318     public static String roleToString(@Role int role) {
319         return (role == ROLE_OUTPUT ? "output" : "input");
320     }
321 
322     @Override
toString()323     public String toString() {
324         return new String("AudioDeviceAttributes:"
325                 + " role:" + roleToString(mRole)
326                 + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType)
327                         : AudioSystem.getInputDeviceName(mNativeType))
328                 + " addr:" + Utils.anonymizeBluetoothAddress(mNativeType, mAddress)
329                 + " name:" + mName
330                 + " profiles:" + mAudioProfiles.toString()
331                 + " descriptors:" + mAudioDescriptors.toString());
332     }
333 
334     @Override
describeContents()335     public int describeContents() {
336         return 0;
337     }
338 
339     @Override
writeToParcel(@onNull Parcel dest, int flags)340     public void writeToParcel(@NonNull Parcel dest, int flags) {
341         dest.writeInt(mRole);
342         dest.writeInt(mType);
343         dest.writeString(mAddress);
344         dest.writeString(mName);
345         dest.writeInt(mNativeType);
346         dest.writeParcelableArray(
347                 mAudioProfiles.toArray(new AudioProfile[mAudioProfiles.size()]), flags);
348         dest.writeParcelableArray(
349                 mAudioDescriptors.toArray(new AudioDescriptor[mAudioDescriptors.size()]), flags);
350     }
351 
AudioDeviceAttributes(@onNull Parcel in)352     private AudioDeviceAttributes(@NonNull Parcel in) {
353         mRole = in.readInt();
354         mType = in.readInt();
355         mAddress = in.readString();
356         mName = in.readString();
357         mNativeType = in.readInt();
358         AudioProfile[] audioProfilesArray =
359                 in.readParcelableArray(AudioProfile.class.getClassLoader(), AudioProfile.class);
360         mAudioProfiles = new ArrayList<AudioProfile>(Arrays.asList(audioProfilesArray));
361         AudioDescriptor[] audioDescriptorsArray = in.readParcelableArray(
362                 AudioDescriptor.class.getClassLoader(), AudioDescriptor.class);
363         mAudioDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(audioDescriptorsArray));
364     }
365 
366     public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR =
367             new Parcelable.Creator<AudioDeviceAttributes>() {
368         /**
369          * Rebuilds an AudioDeviceAttributes previously stored with writeToParcel().
370          * @param p Parcel object to read the AudioDeviceAttributes from
371          * @return a new AudioDeviceAttributes created from the data in the parcel
372          */
373         public AudioDeviceAttributes createFromParcel(Parcel p) {
374             return new AudioDeviceAttributes(p);
375         }
376 
377         public AudioDeviceAttributes[] newArray(int size) {
378             return new AudioDeviceAttributes[size];
379         }
380     };
381 }
382