| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.usb; |
| |
| import android.annotation.NonNull; |
| import android.media.AudioSystem; |
| import android.media.IAudioService; |
| import android.os.RemoteException; |
| import android.service.usb.UsbAlsaDeviceProto; |
| import android.util.Slog; |
| |
| import com.android.internal.util.dump.DualDumpOutputStream; |
| import com.android.server.audio.AudioService; |
| |
| /** |
| * Represents the ALSA specification, and attributes of an ALSA device. |
| */ |
| public final class UsbAlsaDevice { |
| private static final String TAG = "UsbAlsaDevice"; |
| protected static final boolean DEBUG = false; |
| |
| private final int mCardNum; |
| private final int mDeviceNum; |
| private final String mDeviceAddress; |
| private final boolean mHasOutput; |
| private final boolean mHasInput; |
| |
| private final boolean mIsInputHeadset; |
| private final boolean mIsOutputHeadset; |
| |
| private boolean mSelected = false; |
| private int mOutputState; |
| private int mInputState; |
| private UsbAlsaJackDetector mJackDetector; |
| private IAudioService mAudioService; |
| |
| private String mDeviceName = ""; |
| private String mDeviceDescription = ""; |
| |
| public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, |
| boolean hasOutput, boolean hasInput, |
| boolean isInputHeadset, boolean isOutputHeadset) { |
| mAudioService = audioService; |
| mCardNum = card; |
| mDeviceNum = device; |
| mDeviceAddress = deviceAddress; |
| mHasOutput = hasOutput; |
| mHasInput = hasInput; |
| mIsInputHeadset = isInputHeadset; |
| mIsOutputHeadset = isOutputHeadset; |
| } |
| |
| /** |
| * @returns the ALSA card number associated with this peripheral. |
| */ |
| public int getCardNum() { |
| return mCardNum; |
| } |
| |
| /** |
| * @returns the ALSA device number associated with this peripheral. |
| */ |
| public int getDeviceNum() { |
| return mDeviceNum; |
| } |
| |
| /** |
| * @returns the USB device device address associated with this peripheral. |
| */ |
| public String getDeviceAddress() { |
| return mDeviceAddress; |
| } |
| |
| /** |
| * @returns the ALSA card/device address string. |
| */ |
| public String getAlsaCardDeviceString() { |
| if (mCardNum < 0 || mDeviceNum < 0) { |
| Slog.e(TAG, "Invalid alsa card or device alsaCard: " + mCardNum |
| + " alsaDevice: " + mDeviceNum); |
| return null; |
| } |
| return AudioService.makeAlsaAddressString(mCardNum, mDeviceNum); |
| } |
| |
| /** |
| * @returns true if the device supports output. |
| */ |
| public boolean hasOutput() { |
| return mHasOutput; |
| } |
| |
| /** |
| * @returns true if the device supports input (recording). |
| */ |
| public boolean hasInput() { |
| return mHasInput; |
| } |
| |
| /** |
| * @returns true if the device is a headset for purposes of input. |
| */ |
| public boolean isInputHeadset() { |
| return mIsInputHeadset; |
| } |
| |
| /** |
| * @returns true if the device is a headset for purposes of output. |
| */ |
| public boolean isOutputHeadset() { |
| return mIsOutputHeadset; |
| } |
| |
| /** |
| * @returns true if input jack is detected or jack detection is not supported. |
| */ |
| private synchronized boolean isInputJackConnected() { |
| if (mJackDetector == null) { |
| return true; // If jack detect isn't supported, say it's connected. |
| } |
| return mJackDetector.isInputJackConnected(); |
| } |
| |
| /** |
| * @returns true if input jack is detected or jack detection is not supported. |
| */ |
| private synchronized boolean isOutputJackConnected() { |
| if (mJackDetector == null) { |
| return true; // if jack detect isn't supported, say it's connected. |
| } |
| return mJackDetector.isOutputJackConnected(); |
| } |
| |
| /** Begins a jack-detection thread. */ |
| private synchronized void startJackDetect() { |
| // If no jack detect capabilities exist, mJackDetector will be null. |
| mJackDetector = UsbAlsaJackDetector.startJackDetect(this); |
| } |
| |
| /** Stops a jack-detection thread. */ |
| private synchronized void stopJackDetect() { |
| if (mJackDetector != null) { |
| mJackDetector.pleaseStop(); |
| } |
| mJackDetector = null; |
| } |
| |
| /** Start using this device as the selected USB Audio Device. */ |
| public synchronized void start() { |
| mSelected = true; |
| mInputState = 0; |
| mOutputState = 0; |
| startJackDetect(); |
| updateWiredDeviceConnectionState(true); |
| } |
| |
| /** Stop using this device as the selected USB Audio Device. */ |
| public synchronized void stop() { |
| stopJackDetect(); |
| updateWiredDeviceConnectionState(false); |
| mSelected = false; |
| } |
| |
| /** Updates AudioService with the connection state of the alsaDevice. |
| * Checks ALSA Jack state for inputs and outputs before reporting. |
| */ |
| public synchronized void updateWiredDeviceConnectionState(boolean enable) { |
| if (!mSelected) { |
| Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!"); |
| return; |
| } |
| String alsaCardDeviceString = getAlsaCardDeviceString(); |
| if (alsaCardDeviceString == null) { |
| return; |
| } |
| try { |
| // Output Device |
| if (mHasOutput) { |
| int device = mIsOutputHeadset |
| ? AudioSystem.DEVICE_OUT_USB_HEADSET |
| : AudioSystem.DEVICE_OUT_USB_DEVICE; |
| if (DEBUG) { |
| Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device) |
| + " addr:" + alsaCardDeviceString |
| + " name:" + mDeviceName); |
| } |
| boolean connected = isOutputJackConnected(); |
| Slog.i(TAG, "OUTPUT JACK connected: " + connected); |
| int outputState = (enable && connected) ? 1 : 0; |
| if (outputState != mOutputState) { |
| mOutputState = outputState; |
| mAudioService.setWiredDeviceConnectionState(device, outputState, |
| alsaCardDeviceString, |
| mDeviceName, TAG); |
| } |
| } |
| |
| // Input Device |
| if (mHasInput) { |
| int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET |
| : AudioSystem.DEVICE_IN_USB_DEVICE; |
| boolean connected = isInputJackConnected(); |
| Slog.i(TAG, "INPUT JACK connected: " + connected); |
| int inputState = (enable && connected) ? 1 : 0; |
| if (inputState != mInputState) { |
| mInputState = inputState; |
| mAudioService.setWiredDeviceConnectionState( |
| device, inputState, alsaCardDeviceString, |
| mDeviceName, TAG); |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); |
| } |
| } |
| |
| |
| /** |
| * @Override |
| * @returns a string representation of the object. |
| */ |
| public synchronized String toString() { |
| return "UsbAlsaDevice: [card: " + mCardNum |
| + ", device: " + mDeviceNum |
| + ", name: " + mDeviceName |
| + ", hasOutput: " + mHasOutput |
| + ", hasInput: " + mHasInput + "]"; |
| } |
| |
| /** |
| * Write a description of the device to a dump stream. |
| */ |
| public synchronized void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { |
| long token = dump.start(idName, id); |
| |
| dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum); |
| dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum); |
| dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName); |
| dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput); |
| dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput); |
| dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress); |
| |
| dump.end(token); |
| } |
| |
| // called by logDevices |
| synchronized String toShortString() { |
| return "[card:" + mCardNum + " device:" + mDeviceNum + " " + mDeviceName + "]"; |
| } |
| |
| synchronized String getDeviceName() { |
| return mDeviceName; |
| } |
| |
| synchronized void setDeviceNameAndDescription(String deviceName, String deviceDescription) { |
| mDeviceName = deviceName; |
| mDeviceDescription = deviceDescription; |
| } |
| |
| /** |
| * @Override |
| * @returns true if the objects are equivalent. |
| */ |
| public boolean equals(Object obj) { |
| if (!(obj instanceof UsbAlsaDevice)) { |
| return false; |
| } |
| UsbAlsaDevice other = (UsbAlsaDevice) obj; |
| return (mCardNum == other.mCardNum |
| && mDeviceNum == other.mDeviceNum |
| && mHasOutput == other.mHasOutput |
| && mHasInput == other.mHasInput |
| && mIsInputHeadset == other.mIsInputHeadset |
| && mIsOutputHeadset == other.mIsOutputHeadset); |
| } |
| |
| /** |
| * @Override |
| * @returns a hash code generated from the object contents. |
| */ |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + mCardNum; |
| result = prime * result + mDeviceNum; |
| result = prime * result + (mHasOutput ? 0 : 1); |
| result = prime * result + (mHasInput ? 0 : 1); |
| result = prime * result + (mIsInputHeadset ? 0 : 1); |
| result = prime * result + (mIsOutputHeadset ? 0 : 1); |
| |
| return result; |
| } |
| } |
| |