1 /*
2  * Copyright (C) 2014 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.usb;
18 
19 import android.annotation.NonNull;
20 import android.media.AudioDeviceAttributes;
21 import android.media.AudioSystem;
22 import android.media.IAudioService;
23 import android.os.RemoteException;
24 import android.service.usb.UsbAlsaDeviceProto;
25 import android.util.Slog;
26 
27 import com.android.internal.util.dump.DualDumpOutputStream;
28 import com.android.server.audio.AudioService;
29 
30 import java.util.Arrays;
31 
32 /**
33  * Represents the ALSA specification, and attributes of an ALSA device.
34  */
35 public final class UsbAlsaDevice {
36     private static final String TAG = "UsbAlsaDevice";
37     protected static final boolean DEBUG = false;
38 
39     private final int mCardNum;
40     private final int mDeviceNum;
41     private final String mAlsaCardDeviceString;
42     private final String mDeviceAddress;
43 
44     // The following two constant will be used as index to access arrays.
45     private static final int INPUT = 0;
46     private static final int OUTPUT = 1;
47     private static final int NUM_DIRECTIONS = 2;
48     private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
49     private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
50 
51     private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
52     private final boolean mIsDock;
53     private final int[] mDeviceType = new int[NUM_DIRECTIONS];
54     private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
55     private int[] mState = new int[NUM_DIRECTIONS];
56     private UsbAlsaJackDetector mJackDetector;
57     private IAudioService mAudioService;
58 
59     private String mDeviceName = "";
60     private String mDeviceDescription = "";
61 
62     private boolean mHasJackDetect = true;
63 
UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, boolean isInputHeadset, boolean isOutputHeadset, boolean isDock)64     public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
65             boolean hasOutput, boolean hasInput,
66             boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
67         mAudioService = audioService;
68         mCardNum = card;
69         mDeviceNum = device;
70         mDeviceAddress = deviceAddress;
71         mHasDevice[OUTPUT] = hasOutput;
72         mHasDevice[INPUT] = hasInput;
73         mIsHeadset[INPUT] = isInputHeadset;
74         mIsHeadset[OUTPUT] = isOutputHeadset;
75         mIsDock = isDock;
76         initDeviceType();
77         mAlsaCardDeviceString = getAlsaCardDeviceString();
78     }
79 
80     /**
81      * @return the ALSA card number associated with this peripheral.
82      */
getCardNum()83     public int getCardNum() {
84         return mCardNum;
85     }
86 
87     /**
88      * @return the ALSA device number associated with this peripheral.
89      */
getDeviceNum()90     public int getDeviceNum() {
91         return mDeviceNum;
92     }
93 
94     /**
95      * @return the USB device device address associated with this peripheral.
96      */
getDeviceAddress()97     public String getDeviceAddress() {
98         return mDeviceAddress;
99     }
100 
101     /**
102      * @return the ALSA card/device address string.
103      */
getAlsaCardDeviceString()104     public String getAlsaCardDeviceString() {
105         if (mCardNum < 0 || mDeviceNum < 0) {
106             Slog.e(TAG, "Invalid alsa card or device alsaCard: " + mCardNum
107                         + " alsaDevice: " + mDeviceNum);
108             return null;
109         }
110         return AudioService.makeAlsaAddressString(mCardNum, mDeviceNum);
111     }
112 
113     /**
114      * @return true if the device supports output.
115      */
hasOutput()116     public boolean hasOutput() {
117         return mHasDevice[OUTPUT];
118     }
119 
120     /**
121      * @return true if the device supports input (recording).
122      */
hasInput()123     public boolean hasInput() {
124         return mHasDevice[INPUT];
125     }
126 
127     /**
128      * @return true if the device is a headset for purposes of output.
129      */
isOutputHeadset()130     public boolean isOutputHeadset() {
131         return mIsHeadset[OUTPUT];
132     }
133 
134     /**
135      * @return true if the device is a headset for purposes of input.
136      */
isInputHeadset()137     public boolean isInputHeadset() {
138         return mIsHeadset[INPUT];
139     }
140 
141     /**
142      * @return true if the device is a USB dock.
143      */
isDock()144     public boolean isDock() {
145         return mIsDock;
146     }
147 
148     /**
149      * @return true if input jack is detected or jack detection is not supported.
150      */
isInputJackConnected()151     private synchronized boolean isInputJackConnected() {
152         if (mJackDetector == null) {
153             return true;  // If jack detect isn't supported, say it's connected.
154         }
155         return mJackDetector.isInputJackConnected();
156     }
157 
158     /**
159      * @return true if input jack is detected or jack detection is not supported.
160      */
isOutputJackConnected()161     private synchronized boolean isOutputJackConnected() {
162         if (mJackDetector == null) {
163             return true;  // if jack detect isn't supported, say it's connected.
164         }
165         return mJackDetector.isOutputJackConnected();
166     }
167 
168     /** Begins a jack-detection thread. */
startJackDetect()169     private synchronized void startJackDetect() {
170         if (mJackDetector != null) {
171             return;
172         }
173         if (!mHasJackDetect) {
174             return;
175         }
176         // If no jack detect capabilities exist, mJackDetector will be null.
177         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
178         if (mJackDetector == null) {
179             mHasJackDetect = false;
180         }
181     }
182 
183     /** Stops a jack-detection thread. */
stopJackDetect()184     private synchronized void stopJackDetect() {
185         if (mJackDetector != null) {
186             mJackDetector.pleaseStop();
187         }
188         mJackDetector = null;
189     }
190 
191     /** Start using this device as the selected USB Audio Device. */
start()192     public synchronized void start() {
193         startOutput();
194         startInput();
195     }
196 
197     /** Start using this device as the selected USB input device. */
startInput()198     public synchronized void startInput() {
199         startDevice(INPUT);
200     }
201 
202     /** Start using this device as selected USB output device. */
startOutput()203     public synchronized void startOutput() {
204         startDevice(OUTPUT);
205     }
206 
startDevice(int direction)207     private void startDevice(int direction) {
208         if (mIsSelected[direction]) {
209             return;
210         }
211         mIsSelected[direction] = true;
212         mState[direction] = 0;
213         startJackDetect();
214         updateWiredDeviceConnectionState(direction, true /*enable*/);
215     }
216 
217     /** Stop using this device as the selected USB Audio Device. */
stop()218     public synchronized void stop() {
219         stopOutput();
220         stopInput();
221     }
222 
223     /** Stop using this device as the selected USB input device. */
stopInput()224     public synchronized void stopInput() {
225         if (!mIsSelected[INPUT]) {
226             return;
227         }
228         if (!mIsSelected[OUTPUT]) {
229             // Stop jack detection when both input and output are stopped
230             stopJackDetect();
231         }
232         updateInputWiredDeviceConnectionState(false /*enable*/);
233         mIsSelected[INPUT] = false;
234     }
235 
236     /** Stop using this device as the selected USB output device. */
stopOutput()237     public synchronized void stopOutput() {
238         if (!mIsSelected[OUTPUT]) {
239             return;
240         }
241         if (!mIsSelected[INPUT]) {
242             // Stop jack detection when both input and output are stopped
243             stopJackDetect();
244         }
245         updateOutputWiredDeviceConnectionState(false /*enable*/);
246         mIsSelected[OUTPUT] = false;
247     }
248 
initDeviceType()249     private void initDeviceType() {
250         mDeviceType[INPUT] = mHasDevice[INPUT]
251                 ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
252                                      : AudioSystem.DEVICE_IN_USB_DEVICE)
253                 : AudioSystem.DEVICE_NONE;
254         mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
255                 ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
256                            : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
257                                                  : AudioSystem.DEVICE_OUT_USB_DEVICE))
258                 : AudioSystem.DEVICE_NONE;
259     }
260 
261     /**
262      * @return the output device type that will be used to notify AudioService about device
263      *         connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
264      *         will be returned.
265      */
getOutputDeviceType()266     public int getOutputDeviceType() {
267         return mDeviceType[OUTPUT];
268     }
269 
270     /**
271      * @return the input device type that will be used to notify AudioService about device
272      *         connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
273      *         will be returned.
274      */
getInputDeviceType()275     public int getInputDeviceType() {
276         return mDeviceType[INPUT];
277     }
278 
updateWiredDeviceConnectionState(int direction, boolean enable)279     private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
280         if (!mIsSelected[direction]) {
281             Slog.e(TAG, "Updating wired device connection state on unselected device");
282             return false;
283         }
284         if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
285             Slog.d(TAG,
286                     "Unable to set device connection state as " + DIRECTION_STR[direction]
287                     + " device type is none");
288             return false;
289         }
290         if (mAlsaCardDeviceString == null) {
291             Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
292                     + "state failed as alsa card device string is null");
293             return false;
294         }
295         if (DEBUG) {
296             Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
297                     + " addr:" + mAlsaCardDeviceString
298                     + " name:" + mDeviceName);
299         }
300         boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
301         Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
302         int state = (enable && connected) ? 1 : 0;
303         if (state != mState[direction]) {
304             mState[direction] = state;
305             AudioDeviceAttributes attributes = new AudioDeviceAttributes(
306                     mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
307             try {
308                 mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
309             } catch (RemoteException e) {
310                 Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
311                         + DIRECTION_STR[direction]);
312                 return false;
313             }
314         }
315         return true;
316     }
317 
318     /**
319      * Notify AudioService about the input device connection state.
320      *
321      * @param enable true to notify the device as connected.
322      * @return true only when it successfully notifies AudioService about the device
323      *         connection state.
324      */
updateInputWiredDeviceConnectionState(boolean enable)325     public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
326         return updateWiredDeviceConnectionState(INPUT, enable);
327     }
328 
329     /**
330      * Notify AudioService about the output device connection state.
331      *
332      * @param enable true to notify the device as connected.
333      * @return true only when it successfully notifies AudioService about the device
334      *         connection state.
335      */
updateOutputWiredDeviceConnectionState(boolean enable)336     public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
337         return updateWiredDeviceConnectionState(OUTPUT, enable);
338     }
339 
340     /**
341      * @Override
342      * @return a string representation of the object.
343      */
toString()344     public synchronized String toString() {
345         return "UsbAlsaDevice: [card: " + mCardNum
346             + ", device: " + mDeviceNum
347             + ", name: " + mDeviceName
348             + ", hasOutput: " + mHasDevice[OUTPUT]
349             + ", hasInput: " + mHasDevice[INPUT] + "]";
350     }
351 
352     /**
353      * Write a description of the device to a dump stream.
354      */
dump(@onNull DualDumpOutputStream dump, String idName, long id)355     public synchronized void dump(@NonNull DualDumpOutputStream dump, String idName, long id) {
356         long token = dump.start(idName, id);
357 
358         dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
359         dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
360         dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
361         dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
362         dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
363         dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
364 
365         dump.end(token);
366     }
367 
368     // called by logDevices
toShortString()369     synchronized String toShortString() {
370         return "[card:" + mCardNum + " device:" + mDeviceNum + " " + mDeviceName + "]";
371     }
372 
getDeviceName()373     synchronized String getDeviceName() {
374         return mDeviceName;
375     }
376 
setDeviceNameAndDescription(String deviceName, String deviceDescription)377     synchronized void setDeviceNameAndDescription(String deviceName, String deviceDescription) {
378         mDeviceName = deviceName;
379         mDeviceDescription = deviceDescription;
380     }
381 
382     /**
383      * @Override
384      * @return true if the objects are equivalent.
385      */
equals(Object obj)386     public boolean equals(Object obj) {
387         if (!(obj instanceof UsbAlsaDevice)) {
388             return false;
389         }
390         UsbAlsaDevice other = (UsbAlsaDevice) obj;
391         return (mCardNum == other.mCardNum
392                 && mDeviceNum == other.mDeviceNum
393                 && Arrays.equals(mHasDevice, other.mHasDevice)
394                 && Arrays.equals(mIsHeadset, other.mIsHeadset)
395                 && mIsDock == other.mIsDock);
396     }
397 
398     /**
399      * @Override
400      * @return a hash code generated from the object contents.
401      */
hashCode()402     public int hashCode() {
403         final int prime = 31;
404         int result = 1;
405         result = prime * result + mCardNum;
406         result = prime * result + mDeviceNum;
407         result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
408         result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
409         result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
410         result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
411         result = prime * result + (mIsDock ? 0 : 1);
412 
413         return result;
414     }
415 }
416 
417