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.AudioSystem;
21 import android.media.IAudioService;
22 import android.os.RemoteException;
23 import android.service.usb.UsbAlsaDeviceProto;
24 import android.util.Slog;
25 
26 import com.android.internal.util.dump.DualDumpOutputStream;
27 import com.android.server.audio.AudioService;
28 
29 /**
30  * Represents the ALSA specification, and attributes of an ALSA device.
31  */
32 public final class UsbAlsaDevice {
33     private static final String TAG = "UsbAlsaDevice";
34     protected static final boolean DEBUG = false;
35 
36     private final int mCardNum;
37     private final int mDeviceNum;
38     private final String mDeviceAddress;
39     private final boolean mHasOutput;
40     private final boolean mHasInput;
41 
42     private final boolean mIsInputHeadset;
43     private final boolean mIsOutputHeadset;
44 
45     private boolean mSelected = false;
46     private int mOutputState;
47     private int mInputState;
48     private UsbAlsaJackDetector mJackDetector;
49     private IAudioService mAudioService;
50 
51     private String mDeviceName = "";
52     private String mDeviceDescription = "";
53 
UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, boolean isInputHeadset, boolean isOutputHeadset)54     public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
55             boolean hasOutput, boolean hasInput,
56             boolean isInputHeadset, boolean isOutputHeadset) {
57         mAudioService = audioService;
58         mCardNum = card;
59         mDeviceNum = device;
60         mDeviceAddress = deviceAddress;
61         mHasOutput = hasOutput;
62         mHasInput = hasInput;
63         mIsInputHeadset = isInputHeadset;
64         mIsOutputHeadset = isOutputHeadset;
65     }
66 
67     /**
68      * @returns the ALSA card number associated with this peripheral.
69      */
getCardNum()70     public int getCardNum() {
71         return mCardNum;
72     }
73 
74     /**
75      * @returns the ALSA device number associated with this peripheral.
76      */
getDeviceNum()77     public int getDeviceNum() {
78         return mDeviceNum;
79     }
80 
81     /**
82      * @returns the USB device device address associated with this peripheral.
83      */
getDeviceAddress()84     public String getDeviceAddress() {
85         return mDeviceAddress;
86     }
87 
88     /**
89      * @returns the ALSA card/device address string.
90      */
getAlsaCardDeviceString()91     public String getAlsaCardDeviceString() {
92         if (mCardNum < 0 || mDeviceNum < 0) {
93             Slog.e(TAG, "Invalid alsa card or device alsaCard: " + mCardNum
94                         + " alsaDevice: " + mDeviceNum);
95             return null;
96         }
97         return AudioService.makeAlsaAddressString(mCardNum, mDeviceNum);
98     }
99 
100     /**
101      * @returns true if the device supports output.
102      */
hasOutput()103     public boolean hasOutput() {
104         return mHasOutput;
105     }
106 
107     /**
108      * @returns true if the device supports input (recording).
109      */
hasInput()110     public boolean hasInput() {
111         return mHasInput;
112     }
113 
114     /**
115      * @returns true if the device is a headset for purposes of input.
116      */
isInputHeadset()117     public boolean isInputHeadset() {
118         return mIsInputHeadset;
119     }
120 
121     /**
122      * @returns true if the device is a headset for purposes of output.
123      */
isOutputHeadset()124     public boolean isOutputHeadset() {
125         return mIsOutputHeadset;
126     }
127 
128     /**
129      * @returns true if input jack is detected or jack detection is not supported.
130      */
isInputJackConnected()131     private synchronized boolean isInputJackConnected() {
132         if (mJackDetector == null) {
133             return true;  // If jack detect isn't supported, say it's connected.
134         }
135         return mJackDetector.isInputJackConnected();
136     }
137 
138     /**
139      * @returns true if input jack is detected or jack detection is not supported.
140      */
isOutputJackConnected()141     private synchronized boolean isOutputJackConnected() {
142         if (mJackDetector == null) {
143             return true;  // if jack detect isn't supported, say it's connected.
144         }
145         return mJackDetector.isOutputJackConnected();
146     }
147 
148     /** Begins a jack-detection thread. */
startJackDetect()149     private synchronized void startJackDetect() {
150         // If no jack detect capabilities exist, mJackDetector will be null.
151         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
152     }
153 
154     /** Stops a jack-detection thread. */
stopJackDetect()155     private synchronized void stopJackDetect() {
156         if (mJackDetector != null) {
157             mJackDetector.pleaseStop();
158         }
159         mJackDetector = null;
160     }
161 
162     /** Start using this device as the selected USB Audio Device. */
start()163     public synchronized void start() {
164         mSelected = true;
165         mInputState = 0;
166         mOutputState = 0;
167         startJackDetect();
168         updateWiredDeviceConnectionState(true);
169     }
170 
171     /** Stop using this device as the selected USB Audio Device. */
stop()172     public synchronized void stop() {
173         stopJackDetect();
174         updateWiredDeviceConnectionState(false);
175         mSelected = false;
176     }
177 
178     /** Updates AudioService with the connection state of the alsaDevice.
179      *  Checks ALSA Jack state for inputs and outputs before reporting.
180      */
updateWiredDeviceConnectionState(boolean enable)181     public synchronized void updateWiredDeviceConnectionState(boolean enable) {
182         if (!mSelected) {
183             Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
184             return;
185         }
186         String alsaCardDeviceString = getAlsaCardDeviceString();
187         if (alsaCardDeviceString == null) {
188             return;
189         }
190         try {
191             // Output Device
192             if (mHasOutput) {
193                 int device = mIsOutputHeadset
194                         ? AudioSystem.DEVICE_OUT_USB_HEADSET
195                         : AudioSystem.DEVICE_OUT_USB_DEVICE;
196                 if (DEBUG) {
197                     Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
198                             + " addr:" + alsaCardDeviceString
199                             + " name:" + mDeviceName);
200                 }
201                 boolean connected = isOutputJackConnected();
202                 Slog.i(TAG, "OUTPUT JACK connected: " + connected);
203                 int outputState = (enable && connected) ? 1 : 0;
204                 if (outputState != mOutputState) {
205                     mOutputState = outputState;
206                     mAudioService.setWiredDeviceConnectionState(device, outputState,
207                                                                 alsaCardDeviceString,
208                                                                 mDeviceName, TAG);
209                 }
210             }
211 
212             // Input Device
213             if (mHasInput) {
214                 int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET
215                         : AudioSystem.DEVICE_IN_USB_DEVICE;
216                 boolean connected = isInputJackConnected();
217                 Slog.i(TAG, "INPUT JACK connected: " + connected);
218                 int inputState = (enable && connected) ? 1 : 0;
219                 if (inputState != mInputState) {
220                     mInputState = inputState;
221                     mAudioService.setWiredDeviceConnectionState(
222                             device, inputState, alsaCardDeviceString,
223                             mDeviceName, TAG);
224                 }
225             }
226         } catch (RemoteException e) {
227             Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
228         }
229     }
230 
231 
232     /**
233      * @Override
234      * @returns a string representation of the object.
235      */
toString()236     public synchronized String toString() {
237         return "UsbAlsaDevice: [card: " + mCardNum
238             + ", device: " + mDeviceNum
239             + ", name: " + mDeviceName
240             + ", hasOutput: " + mHasOutput
241             + ", hasInput: " + mHasInput + "]";
242     }
243 
244     /**
245      * Write a description of the device to a dump stream.
246      */
dump(@onNull DualDumpOutputStream dump, String idName, long id)247     public synchronized void dump(@NonNull DualDumpOutputStream dump, String idName, long id) {
248         long token = dump.start(idName, id);
249 
250         dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
251         dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
252         dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
253         dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
254         dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
255         dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
256 
257         dump.end(token);
258     }
259 
260     // called by logDevices
toShortString()261     synchronized String toShortString() {
262         return "[card:" + mCardNum + " device:" + mDeviceNum + " " + mDeviceName + "]";
263     }
264 
getDeviceName()265     synchronized String getDeviceName() {
266         return mDeviceName;
267     }
268 
setDeviceNameAndDescription(String deviceName, String deviceDescription)269     synchronized void setDeviceNameAndDescription(String deviceName, String deviceDescription) {
270         mDeviceName = deviceName;
271         mDeviceDescription = deviceDescription;
272     }
273 
274     /**
275      * @Override
276      * @returns true if the objects are equivalent.
277      */
equals(Object obj)278     public boolean equals(Object obj) {
279         if (!(obj instanceof UsbAlsaDevice)) {
280             return false;
281         }
282         UsbAlsaDevice other = (UsbAlsaDevice) obj;
283         return (mCardNum == other.mCardNum
284                 && mDeviceNum == other.mDeviceNum
285                 && mHasOutput == other.mHasOutput
286                 && mHasInput == other.mHasInput
287                 && mIsInputHeadset == other.mIsInputHeadset
288                 && mIsOutputHeadset == other.mIsOutputHeadset);
289     }
290 
291     /**
292      * @Override
293      * @returns a hash code generated from the object contents.
294      */
hashCode()295     public int hashCode() {
296         final int prime = 31;
297         int result = 1;
298         result = prime * result + mCardNum;
299         result = prime * result + mDeviceNum;
300         result = prime * result + (mHasOutput ? 0 : 1);
301         result = prime * result + (mHasInput ? 0 : 1);
302         result = prime * result + (mIsInputHeadset ? 0 : 1);
303         result = prime * result + (mIsOutputHeadset ? 0 : 1);
304 
305         return result;
306     }
307 }
308 
309