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.AudioDeviceAttributes; 21 import android.media.AudioSystem; 22 import android.media.IAudioService; 23 import android.os.RemoteException; 24 import android.service.usb.UsbAlsaDeviceProto; 25 import android.util.Slog; 26 27 import com.android.internal.util.dump.DualDumpOutputStream; 28 import com.android.server.audio.AudioService; 29 30 import java.util.Arrays; 31 32 /** 33 * Represents the ALSA specification, and attributes of an ALSA device. 34 */ 35 public final class UsbAlsaDevice { 36 private static final String TAG = "UsbAlsaDevice"; 37 protected static final boolean DEBUG = false; 38 39 private final int mCardNum; 40 private final int mDeviceNum; 41 private final String mAlsaCardDeviceString; 42 private final String mDeviceAddress; 43 44 // The following two constant will be used as index to access arrays. 45 private static final int INPUT = 0; 46 private static final int OUTPUT = 1; 47 private static final int NUM_DIRECTIONS = 2; 48 private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"}; 49 private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS]; 50 51 private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS]; 52 private final boolean mIsDock; 53 private final int[] mDeviceType = new int[NUM_DIRECTIONS]; 54 private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS]; 55 private int[] mState = new int[NUM_DIRECTIONS]; 56 private UsbAlsaJackDetector mJackDetector; 57 private IAudioService mAudioService; 58 59 private String mDeviceName = ""; 60 private String mDeviceDescription = ""; 61 62 private boolean mHasJackDetect = true; 63 UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, boolean isInputHeadset, boolean isOutputHeadset, boolean isDock)64 public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, 65 boolean hasOutput, boolean hasInput, 66 boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) { 67 mAudioService = audioService; 68 mCardNum = card; 69 mDeviceNum = device; 70 mDeviceAddress = deviceAddress; 71 mHasDevice[OUTPUT] = hasOutput; 72 mHasDevice[INPUT] = hasInput; 73 mIsHeadset[INPUT] = isInputHeadset; 74 mIsHeadset[OUTPUT] = isOutputHeadset; 75 mIsDock = isDock; 76 initDeviceType(); 77 mAlsaCardDeviceString = getAlsaCardDeviceString(); 78 } 79 80 /** 81 * @return the ALSA card number associated with this peripheral. 82 */ getCardNum()83 public int getCardNum() { 84 return mCardNum; 85 } 86 87 /** 88 * @return the ALSA device number associated with this peripheral. 89 */ getDeviceNum()90 public int getDeviceNum() { 91 return mDeviceNum; 92 } 93 94 /** 95 * @return the USB device device address associated with this peripheral. 96 */ getDeviceAddress()97 public String getDeviceAddress() { 98 return mDeviceAddress; 99 } 100 101 /** 102 * @return the ALSA card/device address string. 103 */ getAlsaCardDeviceString()104 public String getAlsaCardDeviceString() { 105 if (mCardNum < 0 || mDeviceNum < 0) { 106 Slog.e(TAG, "Invalid alsa card or device alsaCard: " + mCardNum 107 + " alsaDevice: " + mDeviceNum); 108 return null; 109 } 110 return AudioService.makeAlsaAddressString(mCardNum, mDeviceNum); 111 } 112 113 /** 114 * @return true if the device supports output. 115 */ hasOutput()116 public boolean hasOutput() { 117 return mHasDevice[OUTPUT]; 118 } 119 120 /** 121 * @return true if the device supports input (recording). 122 */ hasInput()123 public boolean hasInput() { 124 return mHasDevice[INPUT]; 125 } 126 127 /** 128 * @return true if the device is a headset for purposes of output. 129 */ isOutputHeadset()130 public boolean isOutputHeadset() { 131 return mIsHeadset[OUTPUT]; 132 } 133 134 /** 135 * @return true if the device is a headset for purposes of input. 136 */ isInputHeadset()137 public boolean isInputHeadset() { 138 return mIsHeadset[INPUT]; 139 } 140 141 /** 142 * @return true if the device is a USB dock. 143 */ isDock()144 public boolean isDock() { 145 return mIsDock; 146 } 147 148 /** 149 * @return true if input jack is detected or jack detection is not supported. 150 */ isInputJackConnected()151 private synchronized boolean isInputJackConnected() { 152 if (mJackDetector == null) { 153 return true; // If jack detect isn't supported, say it's connected. 154 } 155 return mJackDetector.isInputJackConnected(); 156 } 157 158 /** 159 * @return true if input jack is detected or jack detection is not supported. 160 */ isOutputJackConnected()161 private synchronized boolean isOutputJackConnected() { 162 if (mJackDetector == null) { 163 return true; // if jack detect isn't supported, say it's connected. 164 } 165 return mJackDetector.isOutputJackConnected(); 166 } 167 168 /** Begins a jack-detection thread. */ startJackDetect()169 private synchronized void startJackDetect() { 170 if (mJackDetector != null) { 171 return; 172 } 173 if (!mHasJackDetect) { 174 return; 175 } 176 // If no jack detect capabilities exist, mJackDetector will be null. 177 mJackDetector = UsbAlsaJackDetector.startJackDetect(this); 178 if (mJackDetector == null) { 179 mHasJackDetect = false; 180 } 181 } 182 183 /** Stops a jack-detection thread. */ stopJackDetect()184 private synchronized void stopJackDetect() { 185 if (mJackDetector != null) { 186 mJackDetector.pleaseStop(); 187 } 188 mJackDetector = null; 189 } 190 191 /** Start using this device as the selected USB Audio Device. */ start()192 public synchronized void start() { 193 startOutput(); 194 startInput(); 195 } 196 197 /** Start using this device as the selected USB input device. */ startInput()198 public synchronized void startInput() { 199 startDevice(INPUT); 200 } 201 202 /** Start using this device as selected USB output device. */ startOutput()203 public synchronized void startOutput() { 204 startDevice(OUTPUT); 205 } 206 startDevice(int direction)207 private void startDevice(int direction) { 208 if (mIsSelected[direction]) { 209 return; 210 } 211 mIsSelected[direction] = true; 212 mState[direction] = 0; 213 startJackDetect(); 214 updateWiredDeviceConnectionState(direction, true /*enable*/); 215 } 216 217 /** Stop using this device as the selected USB Audio Device. */ stop()218 public synchronized void stop() { 219 stopOutput(); 220 stopInput(); 221 } 222 223 /** Stop using this device as the selected USB input device. */ stopInput()224 public synchronized void stopInput() { 225 if (!mIsSelected[INPUT]) { 226 return; 227 } 228 if (!mIsSelected[OUTPUT]) { 229 // Stop jack detection when both input and output are stopped 230 stopJackDetect(); 231 } 232 updateInputWiredDeviceConnectionState(false /*enable*/); 233 mIsSelected[INPUT] = false; 234 } 235 236 /** Stop using this device as the selected USB output device. */ stopOutput()237 public synchronized void stopOutput() { 238 if (!mIsSelected[OUTPUT]) { 239 return; 240 } 241 if (!mIsSelected[INPUT]) { 242 // Stop jack detection when both input and output are stopped 243 stopJackDetect(); 244 } 245 updateOutputWiredDeviceConnectionState(false /*enable*/); 246 mIsSelected[OUTPUT] = false; 247 } 248 initDeviceType()249 private void initDeviceType() { 250 mDeviceType[INPUT] = mHasDevice[INPUT] 251 ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET 252 : AudioSystem.DEVICE_IN_USB_DEVICE) 253 : AudioSystem.DEVICE_NONE; 254 mDeviceType[OUTPUT] = mHasDevice[OUTPUT] 255 ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET 256 : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET 257 : AudioSystem.DEVICE_OUT_USB_DEVICE)) 258 : AudioSystem.DEVICE_NONE; 259 } 260 261 /** 262 * @return the output device type that will be used to notify AudioService about device 263 * connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE} 264 * will be returned. 265 */ getOutputDeviceType()266 public int getOutputDeviceType() { 267 return mDeviceType[OUTPUT]; 268 } 269 270 /** 271 * @return the input device type that will be used to notify AudioService about device 272 * connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE} 273 * will be returned. 274 */ getInputDeviceType()275 public int getInputDeviceType() { 276 return mDeviceType[INPUT]; 277 } 278 updateWiredDeviceConnectionState(int direction, boolean enable)279 private boolean updateWiredDeviceConnectionState(int direction, boolean enable) { 280 if (!mIsSelected[direction]) { 281 Slog.e(TAG, "Updating wired device connection state on unselected device"); 282 return false; 283 } 284 if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) { 285 Slog.d(TAG, 286 "Unable to set device connection state as " + DIRECTION_STR[direction] 287 + " device type is none"); 288 return false; 289 } 290 if (mAlsaCardDeviceString == null) { 291 Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection " 292 + "state failed as alsa card device string is null"); 293 return false; 294 } 295 if (DEBUG) { 296 Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction]) 297 + " addr:" + mAlsaCardDeviceString 298 + " name:" + mDeviceName); 299 } 300 boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected(); 301 Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected); 302 int state = (enable && connected) ? 1 : 0; 303 if (state != mState[direction]) { 304 mState[direction] = state; 305 AudioDeviceAttributes attributes = new AudioDeviceAttributes( 306 mDeviceType[direction], mAlsaCardDeviceString, mDeviceName); 307 try { 308 mAudioService.setWiredDeviceConnectionState(attributes, state, TAG); 309 } catch (RemoteException e) { 310 Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for " 311 + DIRECTION_STR[direction]); 312 return false; 313 } 314 } 315 return true; 316 } 317 318 /** 319 * Notify AudioService about the input device connection state. 320 * 321 * @param enable true to notify the device as connected. 322 * @return true only when it successfully notifies AudioService about the device 323 * connection state. 324 */ updateInputWiredDeviceConnectionState(boolean enable)325 public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) { 326 return updateWiredDeviceConnectionState(INPUT, enable); 327 } 328 329 /** 330 * Notify AudioService about the output device connection state. 331 * 332 * @param enable true to notify the device as connected. 333 * @return true only when it successfully notifies AudioService about the device 334 * connection state. 335 */ updateOutputWiredDeviceConnectionState(boolean enable)336 public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) { 337 return updateWiredDeviceConnectionState(OUTPUT, enable); 338 } 339 340 /** 341 * @Override 342 * @return a string representation of the object. 343 */ toString()344 public synchronized String toString() { 345 return "UsbAlsaDevice: [card: " + mCardNum 346 + ", device: " + mDeviceNum 347 + ", name: " + mDeviceName 348 + ", hasOutput: " + mHasDevice[OUTPUT] 349 + ", hasInput: " + mHasDevice[INPUT] + "]"; 350 } 351 352 /** 353 * Write a description of the device to a dump stream. 354 */ dump(@onNull DualDumpOutputStream dump, String idName, long id)355 public synchronized void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { 356 long token = dump.start(idName, id); 357 358 dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum); 359 dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum); 360 dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName); 361 dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]); 362 dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]); 363 dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress); 364 365 dump.end(token); 366 } 367 368 // called by logDevices toShortString()369 synchronized String toShortString() { 370 return "[card:" + mCardNum + " device:" + mDeviceNum + " " + mDeviceName + "]"; 371 } 372 getDeviceName()373 synchronized String getDeviceName() { 374 return mDeviceName; 375 } 376 setDeviceNameAndDescription(String deviceName, String deviceDescription)377 synchronized void setDeviceNameAndDescription(String deviceName, String deviceDescription) { 378 mDeviceName = deviceName; 379 mDeviceDescription = deviceDescription; 380 } 381 382 /** 383 * @Override 384 * @return true if the objects are equivalent. 385 */ equals(Object obj)386 public boolean equals(Object obj) { 387 if (!(obj instanceof UsbAlsaDevice)) { 388 return false; 389 } 390 UsbAlsaDevice other = (UsbAlsaDevice) obj; 391 return (mCardNum == other.mCardNum 392 && mDeviceNum == other.mDeviceNum 393 && Arrays.equals(mHasDevice, other.mHasDevice) 394 && Arrays.equals(mIsHeadset, other.mIsHeadset) 395 && mIsDock == other.mIsDock); 396 } 397 398 /** 399 * @Override 400 * @return a hash code generated from the object contents. 401 */ hashCode()402 public int hashCode() { 403 final int prime = 31; 404 int result = 1; 405 result = prime * result + mCardNum; 406 result = prime * result + mDeviceNum; 407 result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1); 408 result = prime * result + (mHasDevice[INPUT] ? 0 : 1); 409 result = prime * result + (mIsHeadset[INPUT] ? 0 : 1); 410 result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1); 411 result = prime * result + (mIsDock ? 0 : 1); 412 413 return result; 414 } 415 } 416 417