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