blob: 7fe8582f96de1cb65bdcbff980d92328a6ff0c18 [file] [log] [blame]
/*
* 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.AudioDeviceAttributes;
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;
import java.util.Arrays;
/**
* 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 mAlsaCardDeviceString;
private final String mDeviceAddress;
// The following two constant will be used as index to access arrays.
private static final int INPUT = 0;
private static final int OUTPUT = 1;
private static final int NUM_DIRECTIONS = 2;
private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
private final boolean mIsDock;
private final int[] mDeviceType = new int[NUM_DIRECTIONS];
private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
private int[] mState = new int[NUM_DIRECTIONS];
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, boolean isDock) {
mAudioService = audioService;
mCardNum = card;
mDeviceNum = device;
mDeviceAddress = deviceAddress;
mHasDevice[OUTPUT] = hasOutput;
mHasDevice[INPUT] = hasInput;
mIsHeadset[INPUT] = isInputHeadset;
mIsHeadset[OUTPUT] = isOutputHeadset;
mIsDock = isDock;
initDeviceType();
mAlsaCardDeviceString = getAlsaCardDeviceString();
}
/**
* @return the ALSA card number associated with this peripheral.
*/
public int getCardNum() {
return mCardNum;
}
/**
* @return the ALSA device number associated with this peripheral.
*/
public int getDeviceNum() {
return mDeviceNum;
}
/**
* @return the USB device device address associated with this peripheral.
*/
public String getDeviceAddress() {
return mDeviceAddress;
}
/**
* @return 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);
}
/**
* @return true if the device supports output.
*/
public boolean hasOutput() {
return mHasDevice[OUTPUT];
}
/**
* @return true if the device supports input (recording).
*/
public boolean hasInput() {
return mHasDevice[INPUT];
}
/**
* @return true if the device is a headset for purposes of output.
*/
public boolean isOutputHeadset() {
return mIsHeadset[OUTPUT];
}
/**
* @return true if the device is a headset for purposes of input.
*/
public boolean isInputHeadset() {
return mIsHeadset[INPUT];
}
/**
* @return true if the device is a USB dock.
*/
public boolean isDock() {
return mIsDock;
}
/**
* @return 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();
}
/**
* @return 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 (mJackDetector != null) {
return;
}
// 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() {
startInput();
startOutput();
}
/** Start using this device as the selected USB input device. */
public synchronized void startInput() {
startDevice(INPUT);
}
/** Start using this device as selected USB output device. */
public synchronized void startOutput() {
startDevice(OUTPUT);
}
private void startDevice(int direction) {
if (mIsSelected[direction]) {
return;
}
mIsSelected[direction] = true;
mState[direction] = 0;
startJackDetect();
updateWiredDeviceConnectionState(direction, true /*enable*/);
}
/** Stop using this device as the selected USB Audio Device. */
public synchronized void stop() {
stopInput();
stopOutput();
}
/** Stop using this device as the selected USB input device. */
public synchronized void stopInput() {
if (!mIsSelected[INPUT]) {
return;
}
if (!mIsSelected[OUTPUT]) {
// Stop jack detection when both input and output are stopped
stopJackDetect();
}
updateInputWiredDeviceConnectionState(false /*enable*/);
mIsSelected[INPUT] = false;
}
/** Stop using this device as the selected USB output device. */
public synchronized void stopOutput() {
if (!mIsSelected[OUTPUT]) {
return;
}
if (!mIsSelected[INPUT]) {
// Stop jack detection when both input and output are stopped
stopJackDetect();
}
updateOutputWiredDeviceConnectionState(false /*enable*/);
mIsSelected[OUTPUT] = false;
}
private void initDeviceType() {
mDeviceType[INPUT] = mHasDevice[INPUT]
? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
: AudioSystem.DEVICE_IN_USB_DEVICE)
: AudioSystem.DEVICE_NONE;
mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
: (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
: AudioSystem.DEVICE_OUT_USB_DEVICE))
: AudioSystem.DEVICE_NONE;
}
/**
* @return the output device type that will be used to notify AudioService about device
* connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
* will be returned.
*/
public int getOutputDeviceType() {
return mDeviceType[OUTPUT];
}
/**
* @return the input device type that will be used to notify AudioService about device
* connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
* will be returned.
*/
public int getInputDeviceType() {
return mDeviceType[INPUT];
}
private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
if (!mIsSelected[direction]) {
Slog.e(TAG, "Updating wired device connection state on unselected device");
return false;
}
if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
Slog.d(TAG,
"Unable to set device connection state as " + DIRECTION_STR[direction]
+ " device type is none");
return false;
}
if (mAlsaCardDeviceString == null) {
Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+ "state failed as alsa card device string is null");
return false;
}
if (DEBUG) {
Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+ " addr:" + mAlsaCardDeviceString
+ " name:" + mDeviceName);
}
boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
int state = (enable && connected) ? 1 : 0;
if (state != mState[direction]) {
mState[direction] = state;
AudioDeviceAttributes attributes = new AudioDeviceAttributes(
mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
try {
mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+ DIRECTION_STR[direction]);
return false;
}
}
return true;
}
/**
* Notify AudioService about the input device connection state.
*
* @param enable true to notify the device as connected.
* @return true only when it successfully notifies AudioService about the device
* connection state.
*/
public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
return updateWiredDeviceConnectionState(INPUT, enable);
}
/**
* Notify AudioService about the output device connection state.
*
* @param enable true to notify the device as connected.
* @return true only when it successfully notifies AudioService about the device
* connection state.
*/
public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
return updateWiredDeviceConnectionState(OUTPUT, enable);
}
/**
* @Override
* @return a string representation of the object.
*/
public synchronized String toString() {
return "UsbAlsaDevice: [card: " + mCardNum
+ ", device: " + mDeviceNum
+ ", name: " + mDeviceName
+ ", hasOutput: " + mHasDevice[OUTPUT]
+ ", hasInput: " + mHasDevice[INPUT] + "]";
}
/**
* 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, mHasDevice[OUTPUT]);
dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
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
* @return 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
&& Arrays.equals(mHasDevice, other.mHasDevice)
&& Arrays.equals(mIsHeadset, other.mIsHeadset)
&& mIsDock == other.mIsDock);
}
/**
* @Override
* @return 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 + (mHasDevice[OUTPUT] ? 0 : 1);
result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
result = prime * result + (mIsDock ? 0 : 1);
return result;
}
}