1 /* 2 * Copyright (C) 2008 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; 18 19 import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT; 20 import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT; 21 import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT; 22 import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT_BIT; 23 import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT; 24 import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT; 25 26 import android.content.Context; 27 import android.media.AudioManager; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.PowerManager; 32 import android.os.PowerManager.WakeLock; 33 import android.os.UEventObserver; 34 import android.util.Log; 35 import android.util.Pair; 36 import android.util.Slog; 37 import android.view.InputDevice; 38 39 import com.android.internal.R; 40 import com.android.server.input.InputManagerService; 41 import com.android.server.input.InputManagerService.WiredAccessoryCallbacks; 42 43 import java.io.File; 44 import java.io.FileNotFoundException; 45 import java.io.FileReader; 46 import java.io.IOException; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.Locale; 50 51 /** 52 * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using 53 * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver 54 * subsystem. 55 */ 56 final class WiredAccessoryManager implements WiredAccessoryCallbacks { 57 private static final String TAG = WiredAccessoryManager.class.getSimpleName(); 58 private static final boolean LOG = false; 59 60 private static final int BIT_HEADSET = (1 << 0); 61 private static final int BIT_HEADSET_NO_MIC = (1 << 1); 62 private static final int BIT_USB_HEADSET_ANLG = (1 << 2); 63 private static final int BIT_USB_HEADSET_DGTL = (1 << 3); 64 private static final int BIT_HDMI_AUDIO = (1 << 4); 65 private static final int BIT_LINEOUT = (1 << 5); 66 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET | BIT_HEADSET_NO_MIC | 67 BIT_USB_HEADSET_ANLG | BIT_USB_HEADSET_DGTL | 68 BIT_HDMI_AUDIO | BIT_LINEOUT); 69 70 private static final String NAME_H2W = "h2w"; 71 private static final String NAME_USB_AUDIO = "usb_audio"; 72 private static final String NAME_HDMI_AUDIO = "hdmi_audio"; 73 private static final String NAME_HDMI = "hdmi"; 74 75 private static final int MSG_NEW_DEVICE_STATE = 1; 76 private static final int MSG_SYSTEM_READY = 2; 77 78 private final Object mLock = new Object(); 79 80 private final WakeLock mWakeLock; // held while there is a pending route change 81 private final AudioManager mAudioManager; 82 83 private int mHeadsetState; 84 85 private int mSwitchValues; 86 87 private final WiredAccessoryObserver mObserver; 88 private final WiredAccessoryExtconObserver mExtconObserver; 89 private final InputManagerService mInputManager; 90 91 private final boolean mUseDevInputEventForAudioJack; 92 WiredAccessoryManager(Context context, InputManagerService inputManager)93 public WiredAccessoryManager(Context context, InputManagerService inputManager) { 94 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 95 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager"); 96 mWakeLock.setReferenceCounted(false); 97 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 98 mInputManager = inputManager; 99 100 mUseDevInputEventForAudioJack = 101 context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); 102 103 mExtconObserver = new WiredAccessoryExtconObserver(); 104 mObserver = new WiredAccessoryObserver(); 105 } 106 onSystemReady()107 private void onSystemReady() { 108 if (mUseDevInputEventForAudioJack) { 109 int switchValues = 0; 110 if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) 111 == 1) { 112 switchValues |= SW_HEADPHONE_INSERT_BIT; 113 } 114 if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) 115 == 1) { 116 switchValues |= SW_MICROPHONE_INSERT_BIT; 117 } 118 if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_LINEOUT_INSERT) == 1) { 119 switchValues |= SW_LINEOUT_INSERT_BIT; 120 } 121 notifyWiredAccessoryChanged(0, switchValues, 122 SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT); 123 } 124 125 126 if (ExtconUEventObserver.extconExists()) { 127 if (mUseDevInputEventForAudioJack) { 128 Log.w(TAG, "Both input event and extcon are used for audio jack," 129 + " please just choose one."); 130 } 131 mExtconObserver.init(); 132 } else { 133 mObserver.init(); 134 } 135 } 136 137 @Override notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask)138 public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { 139 if (LOG) { 140 Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos 141 + " bits=" + switchCodeToString(switchValues, switchMask) 142 + " mask=" + Integer.toHexString(switchMask)); 143 } 144 145 synchronized (mLock) { 146 int headset; 147 mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; 148 switch (mSwitchValues & 149 (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) { 150 case 0: 151 headset = 0; 152 break; 153 154 case SW_HEADPHONE_INSERT_BIT: 155 headset = BIT_HEADSET_NO_MIC; 156 break; 157 158 case SW_LINEOUT_INSERT_BIT: 159 headset = BIT_LINEOUT; 160 break; 161 162 case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: 163 headset = BIT_HEADSET; 164 break; 165 166 case SW_MICROPHONE_INSERT_BIT: 167 headset = BIT_HEADSET; 168 break; 169 170 default: 171 headset = 0; 172 break; 173 } 174 175 updateLocked(NAME_H2W, 176 (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset); 177 } 178 } 179 180 @Override systemReady()181 public void systemReady() { 182 synchronized (mLock) { 183 mWakeLock.acquire(); 184 185 Message msg = mHandler.obtainMessage(MSG_SYSTEM_READY, 0, 0, null); 186 mHandler.sendMessage(msg); 187 } 188 } 189 190 /** 191 * Compare the existing headset state with the new state and pass along accordingly. Note 192 * that this only supports a single headset at a time. Inserting both a usb and jacked headset 193 * results in support for the last one plugged in. Similarly, unplugging either is seen as 194 * unplugging all. 195 * 196 * @param newName One of the NAME_xxx variables defined above. 197 * @param newState 0 or one of the BIT_xxx variables defined above. 198 */ updateLocked(String newName, int newState)199 private void updateLocked(String newName, int newState) { 200 // Retain only relevant bits 201 int headsetState = newState & SUPPORTED_HEADSETS; 202 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; 203 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; 204 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT); 205 boolean h2wStateChange = true; 206 boolean usbStateChange = true; 207 if (LOG) { 208 Slog.v(TAG, "newName=" + newName 209 + " newState=" + newState 210 + " headsetState=" + headsetState 211 + " prev headsetState=" + mHeadsetState); 212 } 213 214 if (mHeadsetState == headsetState) { 215 Log.e(TAG, "No state change."); 216 return; 217 } 218 219 // reject all suspect transitions: only accept state changes from: 220 // - a: 0 headset to 1 headset 221 // - b: 1 headset to 0 headset 222 if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) { 223 Log.e(TAG, "Invalid combination, unsetting h2w flag"); 224 h2wStateChange = false; 225 } 226 // - c: 0 usb headset to 1 usb headset 227 // - d: 1 usb headset to 0 usb headset 228 if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) { 229 Log.e(TAG, "Invalid combination, unsetting usb flag"); 230 usbStateChange = false; 231 } 232 if (!h2wStateChange && !usbStateChange) { 233 Log.e(TAG, "invalid transition, returning ..."); 234 return; 235 } 236 237 mWakeLock.acquire(); 238 239 Log.i(TAG, "MSG_NEW_DEVICE_STATE"); 240 Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, 241 mHeadsetState, ""); 242 mHandler.sendMessage(msg); 243 244 mHeadsetState = headsetState; 245 } 246 247 private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { 248 @Override 249 public void handleMessage(Message msg) { 250 switch (msg.what) { 251 case MSG_NEW_DEVICE_STATE: 252 setDevicesState(msg.arg1, msg.arg2, (String) msg.obj); 253 mWakeLock.release(); 254 break; 255 case MSG_SYSTEM_READY: 256 onSystemReady(); 257 mWakeLock.release(); 258 break; 259 } 260 } 261 }; 262 setDevicesState( int headsetState, int prevHeadsetState, String headsetName)263 private void setDevicesState( 264 int headsetState, int prevHeadsetState, String headsetName) { 265 synchronized (mLock) { 266 int allHeadsets = SUPPORTED_HEADSETS; 267 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { 268 if ((curHeadset & allHeadsets) != 0) { 269 setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); 270 allHeadsets &= ~curHeadset; 271 } 272 } 273 } 274 } 275 setDeviceStateLocked(int headset, int headsetState, int prevHeadsetState, String headsetName)276 private void setDeviceStateLocked(int headset, 277 int headsetState, int prevHeadsetState, String headsetName) { 278 if ((headsetState & headset) != (prevHeadsetState & headset)) { 279 int outDevice = 0; 280 int inDevice = 0; 281 int state; 282 283 if ((headsetState & headset) != 0) { 284 state = 1; 285 } else { 286 state = 0; 287 } 288 289 if (headset == BIT_HEADSET) { 290 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET; 291 inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET; 292 } else if (headset == BIT_HEADSET_NO_MIC) { 293 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; 294 } else if (headset == BIT_LINEOUT) { 295 outDevice = AudioManager.DEVICE_OUT_LINE; 296 } else if (headset == BIT_USB_HEADSET_ANLG) { 297 outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; 298 } else if (headset == BIT_USB_HEADSET_DGTL) { 299 outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; 300 } else if (headset == BIT_HDMI_AUDIO) { 301 outDevice = AudioManager.DEVICE_OUT_HDMI; 302 } else { 303 Slog.e(TAG, "setDeviceState() invalid headset type: " + headset); 304 return; 305 } 306 307 if (LOG) { 308 Slog.v(TAG, "headsetName: " + headsetName + 309 (state == 1 ? " connected" : " disconnected")); 310 } 311 312 if (outDevice != 0) { 313 mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName); 314 } 315 if (inDevice != 0) { 316 mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName); 317 } 318 } 319 } 320 switchCodeToString(int switchValues, int switchMask)321 private String switchCodeToString(int switchValues, int switchMask) { 322 StringBuilder sb = new StringBuilder(); 323 if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 && 324 (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) { 325 sb.append("SW_HEADPHONE_INSERT "); 326 } 327 if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 && 328 (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) { 329 sb.append("SW_MICROPHONE_INSERT"); 330 } 331 return sb.toString(); 332 } 333 334 class WiredAccessoryObserver extends UEventObserver { 335 private final List<UEventInfo> mUEventInfo; 336 WiredAccessoryObserver()337 public WiredAccessoryObserver() { 338 mUEventInfo = makeObservedUEventList(); 339 } 340 init()341 void init() { 342 synchronized (mLock) { 343 if (LOG) Slog.v(TAG, "init()"); 344 char[] buffer = new char[1024]; 345 346 for (int i = 0; i < mUEventInfo.size(); ++i) { 347 UEventInfo uei = mUEventInfo.get(i); 348 try { 349 int curState; 350 FileReader file = new FileReader(uei.getSwitchStatePath()); 351 int len = file.read(buffer, 0, 1024); 352 file.close(); 353 curState = Integer.parseInt((new String(buffer, 0, len)).trim()); 354 355 if (curState > 0) { 356 updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); 357 } 358 } catch (FileNotFoundException e) { 359 Slog.w(TAG, uei.getSwitchStatePath() + 360 " not found while attempting to determine initial switch state"); 361 } catch (Exception e) { 362 Slog.e(TAG, "Error while attempting to determine initial switch state for " 363 + uei.getDevName(), e); 364 } 365 } 366 } 367 368 // At any given time accessories could be inserted 369 // one on the board, one on the dock and one on HDMI: 370 // observe three UEVENTs 371 for (int i = 0; i < mUEventInfo.size(); ++i) { 372 UEventInfo uei = mUEventInfo.get(i); 373 startObserving("DEVPATH=" + uei.getDevPath()); 374 } 375 } 376 makeObservedUEventList()377 private List<UEventInfo> makeObservedUEventList() { 378 List<UEventInfo> retVal = new ArrayList<UEventInfo>(); 379 UEventInfo uei; 380 381 // Monitor h2w 382 if (!mUseDevInputEventForAudioJack) { 383 uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT); 384 if (uei.checkSwitchExists()) { 385 retVal.add(uei); 386 } else { 387 Slog.w(TAG, "This kernel does not have wired headset support"); 388 } 389 } 390 391 // Monitor USB 392 uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL, 0); 393 if (uei.checkSwitchExists()) { 394 retVal.add(uei); 395 } else { 396 Slog.w(TAG, "This kernel does not have usb audio support"); 397 } 398 399 // Monitor HDMI 400 // 401 // If the kernel has support for the "hdmi_audio" switch, use that. It will be 402 // signalled only when the HDMI driver has a video mode configured, and the downstream 403 // sink indicates support for audio in its EDID. 404 // 405 // If the kernel does not have an "hdmi_audio" switch, just fall back on the older 406 // "hdmi" switch instead. 407 uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0, 0); 408 if (uei.checkSwitchExists()) { 409 retVal.add(uei); 410 } else { 411 uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0, 0); 412 if (uei.checkSwitchExists()) { 413 retVal.add(uei); 414 } else { 415 Slog.w(TAG, "This kernel does not have HDMI audio support"); 416 } 417 } 418 419 return retVal; 420 } 421 422 @Override onUEvent(UEventObserver.UEvent event)423 public void onUEvent(UEventObserver.UEvent event) { 424 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); 425 426 try { 427 String devPath = event.get("DEVPATH"); 428 String name = event.get("SWITCH_NAME"); 429 int state = Integer.parseInt(event.get("SWITCH_STATE")); 430 synchronized (mLock) { 431 updateStateLocked(devPath, name, state); 432 } 433 } catch (NumberFormatException e) { 434 Slog.e(TAG, "Could not parse switch state from event " + event); 435 } 436 } 437 updateStateLocked(String devPath, String name, int state)438 private void updateStateLocked(String devPath, String name, int state) { 439 for (int i = 0; i < mUEventInfo.size(); ++i) { 440 UEventInfo uei = mUEventInfo.get(i); 441 if (devPath.equals(uei.getDevPath())) { 442 updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); 443 return; 444 } 445 } 446 } 447 448 private final class UEventInfo { 449 private final String mDevName; 450 private final int mState1Bits; 451 private final int mState2Bits; 452 private final int mStateNbits; 453 UEventInfo(String devName, int state1Bits, int state2Bits, int stateNbits)454 public UEventInfo(String devName, int state1Bits, int state2Bits, int stateNbits) { 455 mDevName = devName; 456 mState1Bits = state1Bits; 457 mState2Bits = state2Bits; 458 mStateNbits = stateNbits; 459 } 460 getDevName()461 public String getDevName() { 462 return mDevName; 463 } 464 getDevPath()465 public String getDevPath() { 466 return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName); 467 } 468 getSwitchStatePath()469 public String getSwitchStatePath() { 470 return String.format(Locale.US, "/sys/class/switch/%s/state", mDevName); 471 } 472 checkSwitchExists()473 public boolean checkSwitchExists() { 474 File f = new File(getSwitchStatePath()); 475 return f.exists(); 476 } 477 computeNewHeadsetState(int headsetState, int switchState)478 public int computeNewHeadsetState(int headsetState, int switchState) { 479 int preserveMask = ~(mState1Bits | mState2Bits | mStateNbits); 480 int setBits = ((switchState == 1) ? mState1Bits : 481 ((switchState == 2) ? mState2Bits : 482 ((switchState == mStateNbits) ? mStateNbits : 0))); 483 484 return ((headsetState & preserveMask) | setBits); 485 } 486 } 487 } 488 489 private class WiredAccessoryExtconObserver extends ExtconStateObserver<Pair<Integer, Integer>> { 490 private final List<ExtconInfo> mExtconInfos; 491 WiredAccessoryExtconObserver()492 WiredAccessoryExtconObserver() { 493 mExtconInfos = ExtconInfo.getExtconInfoForTypes(new String[] { 494 ExtconInfo.EXTCON_HEADPHONE, 495 ExtconInfo.EXTCON_MICROPHONE, 496 ExtconInfo.EXTCON_HDMI, 497 ExtconInfo.EXTCON_LINE_OUT, 498 }); 499 } 500 init()501 private void init() { 502 for (ExtconInfo extconInfo : mExtconInfos) { 503 Pair<Integer, Integer> state = null; 504 try { 505 state = parseStateFromFile(extconInfo); 506 } catch (FileNotFoundException e) { 507 Slog.w(TAG, extconInfo.getStatePath() 508 + " not found while attempting to determine initial state", e); 509 } catch (IOException e) { 510 Slog.e( 511 TAG, 512 "Error reading " + extconInfo.getStatePath() 513 + " while attempting to determine initial state", 514 e); 515 } 516 if (state != null) { 517 updateState(extconInfo, extconInfo.getName(), state); 518 } 519 if (LOG) Slog.d(TAG, "observing " + extconInfo.getName()); 520 startObserving(extconInfo); 521 } 522 523 } 524 525 @Override parseState(ExtconInfo extconInfo, String status)526 public Pair<Integer, Integer> parseState(ExtconInfo extconInfo, String status) { 527 if (LOG) Slog.v(TAG, "status " + status); 528 int[] maskAndState = {0, 0}; 529 // extcon event state changes from kernel4.9 530 // new state will be like STATE=MICROPHONE=1\nHEADPHONE=0 531 if (extconInfo.hasCableType(ExtconInfo.EXTCON_HEADPHONE)) { 532 updateBit(maskAndState, BIT_HEADSET_NO_MIC, status, ExtconInfo.EXTCON_HEADPHONE); 533 } 534 if (extconInfo.hasCableType(ExtconInfo.EXTCON_MICROPHONE)) { 535 updateBit(maskAndState, BIT_HEADSET, status, ExtconInfo.EXTCON_MICROPHONE); 536 } 537 if (extconInfo.hasCableType(ExtconInfo.EXTCON_HDMI)) { 538 updateBit(maskAndState, BIT_HDMI_AUDIO, status, ExtconInfo.EXTCON_HDMI); 539 } 540 if (extconInfo.hasCableType(ExtconInfo.EXTCON_LINE_OUT)) { 541 updateBit(maskAndState, BIT_LINEOUT, status, ExtconInfo.EXTCON_LINE_OUT); 542 } 543 if (LOG) Slog.v(TAG, "mask " + maskAndState[0] + " state " + maskAndState[1]); 544 return Pair.create(maskAndState[0], maskAndState[1]); 545 } 546 547 @Override updateState(ExtconInfo extconInfo, String name, Pair<Integer, Integer> maskAndState)548 public void updateState(ExtconInfo extconInfo, String name, 549 Pair<Integer, Integer> maskAndState) { 550 synchronized (mLock) { 551 int mask = maskAndState.first; 552 int state = maskAndState.second; 553 updateLocked(name, mHeadsetState & ~(mask & ~state) | (mask & state)); 554 return; 555 } 556 } 557 } 558 559 /** 560 * Updates the mask bit at {@code position} to 1 and the state bit at {@code position} to true 561 * if {@code name=1} or false if {}@code name=0} is contained in {@code state}. 562 */ updateBit(int[] maskAndState, int position, String state, String name)563 private static void updateBit(int[] maskAndState, int position, String state, String name) { 564 maskAndState[0] |= position; 565 if (state.contains(name + "=1")) { 566 maskAndState[0] |= position; 567 maskAndState[1] |= position; 568 } else if (state.contains(name + "=0")) { 569 maskAndState[0] |= position; 570 maskAndState[1] &= ~position; 571 } 572 } 573 } 574