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 android.app.ActivityManagerNative; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.os.Handler; 25 import android.os.Message; 26 import android.os.PowerManager; 27 import android.os.PowerManager.WakeLock; 28 import android.os.UEventObserver; 29 import android.util.Slog; 30 import android.media.AudioManager; 31 import android.util.Log; 32 33 import java.io.File; 34 import java.io.FileReader; 35 import java.io.FileNotFoundException; 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. 41 */ 42 class WiredAccessoryObserver extends UEventObserver { 43 private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); 44 private static final boolean LOG = true; 45 private static final int BIT_HEADSET = (1 << 0); 46 private static final int BIT_HEADSET_NO_MIC = (1 << 1); 47 private static final int BIT_USB_HEADSET_ANLG = (1 << 2); 48 private static final int BIT_USB_HEADSET_DGTL = (1 << 3); 49 private static final int BIT_HDMI_AUDIO = (1 << 4); 50 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| 51 BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| 52 BIT_HDMI_AUDIO); 53 private static final int HEADSETS_WITH_MIC = BIT_HEADSET; 54 55 private static class UEventInfo { 56 private final String mDevName; 57 private final int mState1Bits; 58 private final int mState2Bits; 59 60 public UEventInfo(String devName, int state1Bits, int state2Bits) { 61 mDevName = devName; 62 mState1Bits = state1Bits; 63 mState2Bits = state2Bits; 64 } 65 66 public String getDevName() { return mDevName; } 67 68 public String getDevPath() { 69 return String.format("/devices/virtual/switch/%s", mDevName); 70 } 71 72 public String getSwitchStatePath() { 73 return String.format("/sys/class/switch/%s/state", mDevName); 74 } 75 76 public boolean checkSwitchExists() { 77 File f = new File(getSwitchStatePath()); 78 return ((null != f) && f.exists()); 79 } 80 81 public int computeNewHeadsetState(int headsetState, int switchState) { 82 int preserveMask = ~(mState1Bits | mState2Bits); 83 int setBits = ((switchState == 1) ? mState1Bits : 84 ((switchState == 2) ? mState2Bits : 0)); 85 86 return ((headsetState & preserveMask) | setBits); 87 } 88 } 89 90 private static List<UEventInfo> makeObservedUEventList() { 91 List<UEventInfo> retVal = new ArrayList<UEventInfo>(); 92 UEventInfo uei; 93 94 // Monitor h2w 95 uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); 96 if (uei.checkSwitchExists()) { 97 retVal.add(uei); 98 } else { 99 Slog.w(TAG, "This kernel does not have wired headset support"); 100 } 101 102 // Monitor USB 103 uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); 104 if (uei.checkSwitchExists()) { 105 retVal.add(uei); 106 } else { 107 Slog.w(TAG, "This kernel does not have usb audio support"); 108 } 109 110 // Monitor HDMI 111 // 112 // If the kernel has support for the "hdmi_audio" switch, use that. It will be signalled 113 // only when the HDMI driver has a video mode configured, and the downstream sink indicates 114 // support for audio in its EDID. 115 // 116 // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi" 117 // switch instead. 118 uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0); 119 if (uei.checkSwitchExists()) { 120 retVal.add(uei); 121 } else { 122 uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); 123 if (uei.checkSwitchExists()) { 124 retVal.add(uei); 125 } else { 126 Slog.w(TAG, "This kernel does not have HDMI audio support"); 127 } 128 } 129 130 return retVal; 131 } 132 133 private static List<UEventInfo> uEventInfo = makeObservedUEventList(); 134 135 private int mHeadsetState; 136 private int mPrevHeadsetState; 137 private String mHeadsetName; 138 139 private final Context mContext; 140 private final WakeLock mWakeLock; // held while there is a pending route change 141 142 private final AudioManager mAudioManager; 143 144 public WiredAccessoryObserver(Context context) { 145 mContext = context; 146 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 147 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); 148 mWakeLock.setReferenceCounted(false); 149 mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 150 151 context.registerReceiver(new BootCompletedReceiver(), 152 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 153 } 154 155 private final class BootCompletedReceiver extends BroadcastReceiver { 156 @Override 157 public void onReceive(Context context, Intent intent) { 158 // At any given time accessories could be inserted 159 // one on the board, one on the dock and one on HDMI: 160 // observe three UEVENTs 161 init(); // set initial status 162 for (int i = 0; i < uEventInfo.size(); ++i) { 163 UEventInfo uei = uEventInfo.get(i); 164 startObserving("DEVPATH="+uei.getDevPath()); 165 } 166 } 167 } 168 169 @Override 170 public void onUEvent(UEventObserver.UEvent event) { 171 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); 172 173 try { 174 String devPath = event.get("DEVPATH"); 175 String name = event.get("SWITCH_NAME"); 176 int state = Integer.parseInt(event.get("SWITCH_STATE")); 177 updateState(devPath, name, state); 178 } catch (NumberFormatException e) { 179 Slog.e(TAG, "Could not parse switch state from event " + event); 180 } 181 } 182 183 private synchronized final void updateState(String devPath, String name, int state) 184 { 185 for (int i = 0; i < uEventInfo.size(); ++i) { 186 UEventInfo uei = uEventInfo.get(i); 187 if (devPath.equals(uei.getDevPath())) { 188 update(name, uei.computeNewHeadsetState(mHeadsetState, state)); 189 return; 190 } 191 } 192 } 193 194 private synchronized final void init() { 195 char[] buffer = new char[1024]; 196 mPrevHeadsetState = mHeadsetState; 197 198 if (LOG) Slog.v(TAG, "init()"); 199 200 for (int i = 0; i < uEventInfo.size(); ++i) { 201 UEventInfo uei = uEventInfo.get(i); 202 try { 203 int curState; 204 FileReader file = new FileReader(uei.getSwitchStatePath()); 205 int len = file.read(buffer, 0, 1024); 206 file.close(); 207 curState = Integer.valueOf((new String(buffer, 0, len)).trim()); 208 209 if (curState > 0) { 210 updateState(uei.getDevPath(), uei.getDevName(), curState); 211 } 212 213 } catch (FileNotFoundException e) { 214 Slog.w(TAG, uei.getSwitchStatePath() + 215 " not found while attempting to determine initial switch state"); 216 } catch (Exception e) { 217 Slog.e(TAG, "" , e); 218 } 219 } 220 } 221 222 private synchronized final void update(String newName, int newState) { 223 // Retain only relevant bits 224 int headsetState = newState & SUPPORTED_HEADSETS; 225 int newOrOld = headsetState | mHeadsetState; 226 int delay = 0; 227 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; 228 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; 229 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); 230 boolean h2wStateChange = true; 231 boolean usbStateChange = true; 232 // reject all suspect transitions: only accept state changes from: 233 // - a: 0 heaset to 1 headset 234 // - b: 1 headset to 0 headset 235 if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," 236 + "mHeadsetState = "+mHeadsetState); 237 if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { 238 Log.e(TAG, "unsetting h2w flag"); 239 h2wStateChange = false; 240 } 241 // - c: 0 usb headset to 1 usb headset 242 // - d: 1 usb headset to 0 usb headset 243 if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { 244 Log.e(TAG, "unsetting usb flag"); 245 usbStateChange = false; 246 } 247 if (!h2wStateChange && !usbStateChange) { 248 Log.e(TAG, "invalid transition, returning ..."); 249 return; 250 } 251 252 mHeadsetName = newName; 253 mPrevHeadsetState = mHeadsetState; 254 mHeadsetState = headsetState; 255 256 mWakeLock.acquire(); 257 mHandler.sendMessage(mHandler.obtainMessage(0, 258 mHeadsetState, 259 mPrevHeadsetState, 260 mHeadsetName)); 261 } 262 263 private synchronized final void setDevicesState(int headsetState, 264 int prevHeadsetState, 265 String headsetName) { 266 int allHeadsets = SUPPORTED_HEADSETS; 267 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { 268 if ((curHeadset & allHeadsets) != 0) { 269 setDeviceState(curHeadset, headsetState, prevHeadsetState, headsetName); 270 allHeadsets &= ~curHeadset; 271 } 272 } 273 } 274 275 private final void setDeviceState(int headset, 276 int headsetState, 277 int prevHeadsetState, 278 String headsetName) { 279 if ((headsetState & headset) != (prevHeadsetState & headset)) { 280 int device; 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 device = AudioManager.DEVICE_OUT_WIRED_HEADSET; 291 } else if (headset == BIT_HEADSET_NO_MIC){ 292 device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; 293 } else if (headset == BIT_USB_HEADSET_ANLG) { 294 device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; 295 } else if (headset == BIT_USB_HEADSET_DGTL) { 296 device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; 297 } else if (headset == BIT_HDMI_AUDIO) { 298 device = AudioManager.DEVICE_OUT_AUX_DIGITAL; 299 } else { 300 Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); 301 return; 302 } 303 304 if (LOG) 305 Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); 306 307 mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); 308 } 309 } 310 311 private final Handler mHandler = new Handler() { 312 @Override 313 public void handleMessage(Message msg) { 314 setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); 315 mWakeLock.release(); 316 } 317 }; 318 } 319