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
17package com.android.server.usb;
18
19import android.annotation.NonNull;
20import android.media.AudioSystem;
21import android.media.IAudioService;
22import android.os.RemoteException;
23import android.service.usb.UsbAlsaDeviceProto;
24import android.util.Slog;
25
26import com.android.internal.util.dump.DualDumpOutputStream;
27import com.android.server.audio.AudioService;
28
29/**
30 * Represents the ALSA specification, and attributes of an ALSA device.
31 */
32public 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
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     */
70    public int getCardNum() {
71        return mCardNum;
72    }
73
74    /**
75     * @returns the ALSA device number associated with this peripheral.
76     */
77    public int getDeviceNum() {
78        return mDeviceNum;
79    }
80
81    /**
82     * @returns the USB device device address associated with this peripheral.
83     */
84    public String getDeviceAddress() {
85        return mDeviceAddress;
86    }
87
88    /**
89     * @returns the ALSA card/device address string.
90     */
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     */
103    public boolean hasOutput() {
104        return mHasOutput;
105    }
106
107    /**
108     * @returns true if the device supports input (recording).
109     */
110    public boolean hasInput() {
111        return mHasInput;
112    }
113
114    /**
115     * @returns true if the device is a headset for purposes of input.
116     */
117    public boolean isInputHeadset() {
118        return mIsInputHeadset;
119    }
120
121    /**
122     * @returns true if the device is a headset for purposes of output.
123     */
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     */
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     */
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. */
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. */
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. */
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. */
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     */
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     */
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     */
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
261    synchronized String toShortString() {
262        return "[card:" + mCardNum + " device:" + mDeviceNum + " " + mDeviceName + "]";
263    }
264
265    synchronized String getDeviceName() {
266        return mDeviceName;
267    }
268
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     */
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     */
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