WiredAccessoryManager.java revision d83a096f299abd9c7fe5e441ef1bb169c314b575
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 17package com.android.server; 18 19import android.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.Message; 26import android.os.PowerManager; 27import android.os.PowerManager.WakeLock; 28import android.os.UEventObserver; 29import android.util.Slog; 30import android.media.AudioManager; 31import android.util.Log; 32import android.view.InputDevice; 33 34import com.android.internal.R; 35import com.android.server.input.InputManagerService; 36import com.android.server.input.InputManagerService.WiredAccessoryCallbacks; 37import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT; 38import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT; 39import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT; 40import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT; 41 42import java.io.File; 43import java.io.FileReader; 44import java.io.FileNotFoundException; 45import java.util.ArrayList; 46import java.util.List; 47import java.util.Locale; 48 49/** 50 * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using 51 * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver 52 * subsystem. 53 */ 54final class WiredAccessoryManager implements WiredAccessoryCallbacks { 55 private static final String TAG = WiredAccessoryManager.class.getSimpleName(); 56 private static final boolean LOG = true; 57 58 private static final int BIT_HEADSET = (1 << 0); 59 private static final int BIT_HEADSET_NO_MIC = (1 << 1); 60 private static final int BIT_USB_HEADSET_ANLG = (1 << 2); 61 private static final int BIT_USB_HEADSET_DGTL = (1 << 3); 62 private static final int BIT_HDMI_AUDIO = (1 << 4); 63 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| 64 BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| 65 BIT_HDMI_AUDIO); 66 67 private static final String NAME_H2W = "h2w"; 68 private static final String NAME_USB_AUDIO = "usb_audio"; 69 private static final String NAME_HDMI_AUDIO = "hdmi_audio"; 70 private static final String NAME_HDMI = "hdmi"; 71 72 private static final int MSG_NEW_DEVICE_STATE = 1; 73 74 private final Object mLock = new Object(); 75 76 private final WakeLock mWakeLock; // held while there is a pending route change 77 private final AudioManager mAudioManager; 78 79 private int mHeadsetState; 80 81 private int mSwitchValues; 82 83 private final WiredAccessoryObserver mObserver; 84 private final InputManagerService mInputManager; 85 86 private final boolean mUseDevInputEventForAudioJack; 87 88 public WiredAccessoryManager(Context context, InputManagerService inputManager) { 89 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 90 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager"); 91 mWakeLock.setReferenceCounted(false); 92 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 93 mInputManager = inputManager; 94 95 mUseDevInputEventForAudioJack = 96 context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); 97 98 mObserver = new WiredAccessoryObserver(); 99 100 IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 101 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 102 context.registerReceiver(new BroadcastReceiver() { 103 @Override 104 public void onReceive(Context ctx, Intent intent) { 105 bootCompleted(); 106 } 107 }, 108 filter, null, null); 109 } 110 111 private void bootCompleted() { 112 if (mUseDevInputEventForAudioJack) { 113 int switchValues = 0; 114 if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) { 115 switchValues |= SW_HEADPHONE_INSERT_BIT; 116 } 117 if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) { 118 switchValues |= SW_MICROPHONE_INSERT_BIT; 119 } 120 notifyWiredAccessoryChanged(0, switchValues, 121 SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT); 122 } 123 124 mObserver.init(); 125 } 126 127 @Override 128 public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { 129 if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos 130 + " bits=" + switchCodeToString(switchValues, switchMask) 131 + " mask=" + Integer.toHexString(switchMask)); 132 133 synchronized (mLock) { 134 int headset; 135 mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; 136 switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) { 137 case 0: 138 headset = 0; 139 break; 140 141 case SW_HEADPHONE_INSERT_BIT: 142 headset = BIT_HEADSET_NO_MIC; 143 break; 144 145 case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: 146 headset = BIT_HEADSET; 147 break; 148 149 case SW_MICROPHONE_INSERT_BIT: 150 headset = BIT_HEADSET; 151 break; 152 153 default: 154 headset = 0; 155 break; 156 } 157 158 updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset); 159 } 160 } 161 162 /** 163 * Compare the existing headset state with the new state and pass along accordingly. Note 164 * that this only supports a single headset at a time. Inserting both a usb and jacked headset 165 * results in support for the last one plugged in. Similarly, unplugging either is seen as 166 * unplugging all. 167 * 168 * @param newName One of the NAME_xxx variables defined above. 169 * @param newState 0 or one of the BIT_xxx variables defined above. 170 */ 171 private void updateLocked(String newName, int newState) { 172 // Retain only relevant bits 173 int headsetState = newState & SUPPORTED_HEADSETS; 174 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; 175 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; 176 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); 177 boolean h2wStateChange = true; 178 boolean usbStateChange = true; 179 if (LOG) Slog.v(TAG, "newName=" + newName 180 + " newState=" + newState 181 + " headsetState=" + headsetState 182 + " prev headsetState=" + mHeadsetState); 183 184 if (mHeadsetState == headsetState) { 185 Log.e(TAG, "No state change."); 186 return; 187 } 188 189 // reject all suspect transitions: only accept state changes from: 190 // - a: 0 headset to 1 headset 191 // - b: 1 headset to 0 headset 192 if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) { 193 Log.e(TAG, "Invalid combination, unsetting h2w flag"); 194 h2wStateChange = false; 195 } 196 // - c: 0 usb headset to 1 usb headset 197 // - d: 1 usb headset to 0 usb headset 198 if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) { 199 Log.e(TAG, "Invalid combination, unsetting usb flag"); 200 usbStateChange = false; 201 } 202 if (!h2wStateChange && !usbStateChange) { 203 Log.e(TAG, "invalid transition, returning ..."); 204 return; 205 } 206 207 mWakeLock.acquire(); 208 209 Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, 210 mHeadsetState, newName); 211 mHandler.sendMessage(msg); 212 213 mHeadsetState = headsetState; 214 } 215 216 private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { 217 @Override 218 public void handleMessage(Message msg) { 219 switch (msg.what) { 220 case MSG_NEW_DEVICE_STATE: 221 setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); 222 mWakeLock.release(); 223 } 224 } 225 }; 226 227 private void setDevicesState( 228 int headsetState, int prevHeadsetState, String headsetName) { 229 synchronized (mLock) { 230 int allHeadsets = SUPPORTED_HEADSETS; 231 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { 232 if ((curHeadset & allHeadsets) != 0) { 233 setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); 234 allHeadsets &= ~curHeadset; 235 } 236 } 237 } 238 } 239 240 private void setDeviceStateLocked(int headset, 241 int headsetState, int prevHeadsetState, String headsetName) { 242 if ((headsetState & headset) != (prevHeadsetState & headset)) { 243 int device; 244 int state; 245 246 if ((headsetState & headset) != 0) { 247 state = 1; 248 } else { 249 state = 0; 250 } 251 252 if (headset == BIT_HEADSET) { 253 device = AudioManager.DEVICE_OUT_WIRED_HEADSET; 254 } else if (headset == BIT_HEADSET_NO_MIC){ 255 device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; 256 } else if (headset == BIT_USB_HEADSET_ANLG) { 257 device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; 258 } else if (headset == BIT_USB_HEADSET_DGTL) { 259 device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; 260 } else if (headset == BIT_HDMI_AUDIO) { 261 device = AudioManager.DEVICE_OUT_AUX_DIGITAL; 262 } else { 263 Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); 264 return; 265 } 266 267 if (LOG) 268 Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); 269 270 mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); 271 } 272 } 273 274 private String switchCodeToString(int switchValues, int switchMask) { 275 StringBuffer sb = new StringBuffer(); 276 if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 && 277 (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) { 278 sb.append("SW_HEADPHONE_INSERT "); 279 } 280 if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 && 281 (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) { 282 sb.append("SW_MICROPHONE_INSERT"); 283 } 284 return sb.toString(); 285 } 286 287 class WiredAccessoryObserver extends UEventObserver { 288 private final List<UEventInfo> mUEventInfo; 289 290 public WiredAccessoryObserver() { 291 mUEventInfo = makeObservedUEventList(); 292 } 293 294 void init() { 295 synchronized (mLock) { 296 if (LOG) Slog.v(TAG, "init()"); 297 char[] buffer = new char[1024]; 298 299 for (int i = 0; i < mUEventInfo.size(); ++i) { 300 UEventInfo uei = mUEventInfo.get(i); 301 try { 302 int curState; 303 FileReader file = new FileReader(uei.getSwitchStatePath()); 304 int len = file.read(buffer, 0, 1024); 305 file.close(); 306 curState = Integer.valueOf((new String(buffer, 0, len)).trim()); 307 308 if (curState > 0) { 309 updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); 310 } 311 } catch (FileNotFoundException e) { 312 Slog.w(TAG, uei.getSwitchStatePath() + 313 " not found while attempting to determine initial switch state"); 314 } catch (Exception e) { 315 Slog.e(TAG, "" , e); 316 } 317 } 318 } 319 320 // At any given time accessories could be inserted 321 // one on the board, one on the dock and one on HDMI: 322 // observe three UEVENTs 323 for (int i = 0; i < mUEventInfo.size(); ++i) { 324 UEventInfo uei = mUEventInfo.get(i); 325 startObserving("DEVPATH="+uei.getDevPath()); 326 } 327 } 328 329 private List<UEventInfo> makeObservedUEventList() { 330 List<UEventInfo> retVal = new ArrayList<UEventInfo>(); 331 UEventInfo uei; 332 333 // Monitor h2w 334 if (!mUseDevInputEventForAudioJack) { 335 uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC); 336 if (uei.checkSwitchExists()) { 337 retVal.add(uei); 338 } else { 339 Slog.w(TAG, "This kernel does not have wired headset support"); 340 } 341 } 342 343 // Monitor USB 344 uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); 345 if (uei.checkSwitchExists()) { 346 retVal.add(uei); 347 } else { 348 Slog.w(TAG, "This kernel does not have usb audio support"); 349 } 350 351 // Monitor HDMI 352 // 353 // If the kernel has support for the "hdmi_audio" switch, use that. It will be 354 // signalled only when the HDMI driver has a video mode configured, and the downstream 355 // sink indicates support for audio in its EDID. 356 // 357 // If the kernel does not have an "hdmi_audio" switch, just fall back on the older 358 // "hdmi" switch instead. 359 uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0); 360 if (uei.checkSwitchExists()) { 361 retVal.add(uei); 362 } else { 363 uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0); 364 if (uei.checkSwitchExists()) { 365 retVal.add(uei); 366 } else { 367 Slog.w(TAG, "This kernel does not have HDMI audio support"); 368 } 369 } 370 371 return retVal; 372 } 373 374 @Override 375 public void onUEvent(UEventObserver.UEvent event) { 376 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); 377 378 try { 379 String devPath = event.get("DEVPATH"); 380 String name = event.get("SWITCH_NAME"); 381 int state = Integer.parseInt(event.get("SWITCH_STATE")); 382 synchronized (mLock) { 383 updateStateLocked(devPath, name, state); 384 } 385 } catch (NumberFormatException e) { 386 Slog.e(TAG, "Could not parse switch state from event " + event); 387 } 388 } 389 390 private void updateStateLocked(String devPath, String name, int state) { 391 for (int i = 0; i < mUEventInfo.size(); ++i) { 392 UEventInfo uei = mUEventInfo.get(i); 393 if (devPath.equals(uei.getDevPath())) { 394 updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); 395 return; 396 } 397 } 398 } 399 400 private final class UEventInfo { 401 private final String mDevName; 402 private final int mState1Bits; 403 private final int mState2Bits; 404 405 public UEventInfo(String devName, int state1Bits, int state2Bits) { 406 mDevName = devName; 407 mState1Bits = state1Bits; 408 mState2Bits = state2Bits; 409 } 410 411 public String getDevName() { return mDevName; } 412 413 public String getDevPath() { 414 return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName); 415 } 416 417 public String getSwitchStatePath() { 418 return String.format(Locale.US, "/sys/class/switch/%s/state", mDevName); 419 } 420 421 public boolean checkSwitchExists() { 422 File f = new File(getSwitchStatePath()); 423 return f.exists(); 424 } 425 426 public int computeNewHeadsetState(int headsetState, int switchState) { 427 int preserveMask = ~(mState1Bits | mState2Bits); 428 int setBits = ((switchState == 1) ? mState1Bits : 429 ((switchState == 2) ? mState2Bits : 0)); 430 431 return ((headsetState & preserveMask) | setBits); 432 } 433 } 434 } 435} 436