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.FileReader; 34 import java.io.FileNotFoundException; 35 36 /** 37 * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. 38 */ 39 class WiredAccessoryObserver extends UEventObserver { 40 private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); 41 private static final boolean LOG = true; 42 private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ 43 private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w", 44 "/sys/class/switch/h2w/state", 45 "/sys/class/switch/h2w/name"}, 46 {"DEVPATH=/devices/virtual/switch/usb_audio", 47 "/sys/class/switch/usb_audio/state", 48 "/sys/class/switch/usb_audio/name"}, 49 {"DEVPATH=/devices/virtual/switch/hdmi", 50 "/sys/class/switch/hdmi/state", 51 "/sys/class/switch/hdmi/name"} }; 52 53 private static final int BIT_HEADSET = (1 << 0); 54 private static final int BIT_HEADSET_NO_MIC = (1 << 1); 55 private static final int BIT_USB_HEADSET_ANLG = (1 << 2); 56 private static final int BIT_USB_HEADSET_DGTL = (1 << 3); 57 private static final int BIT_HDMI_AUDIO = (1 << 4); 58 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| 59 BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| 60 BIT_HDMI_AUDIO); 61 private static final int HEADSETS_WITH_MIC = BIT_HEADSET; 62 63 private int mHeadsetState; 64 private int mPrevHeadsetState; 65 private String mHeadsetName; 66 private int switchState; 67 68 private final Context mContext; 69 private final WakeLock mWakeLock; // held while there is a pending route change 70 71 public WiredAccessoryObserver(Context context) { 72 mContext = context; 73 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 74 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); 75 mWakeLock.setReferenceCounted(false); 76 77 context.registerReceiver(new BootCompletedReceiver(), 78 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 79 } 80 81 private final class BootCompletedReceiver extends BroadcastReceiver { 82 @Override 83 public void onReceive(Context context, Intent intent) { 84 // At any given time accessories could be inserted 85 // one on the board, one on the dock and one on HDMI: 86 // observe three UEVENTs 87 init(); // set initial status 88 for (int i = 0; i < MAX_AUDIO_PORTS; i++) { 89 startObserving(uEventInfo[i][0]); 90 } 91 } 92 } 93 94 @Override 95 public void onUEvent(UEventObserver.UEvent event) { 96 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); 97 98 try { 99 String name = event.get("SWITCH_NAME"); 100 int state = Integer.parseInt(event.get("SWITCH_STATE")); 101 updateState(name, state); 102 } catch (NumberFormatException e) { 103 Slog.e(TAG, "Could not parse switch state from event " + event); 104 } 105 } 106 107 private synchronized final void updateState(String name, int state) 108 { 109 if (name.equals("usb_audio")) { 110 switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) | 111 ((state == 1) ? BIT_USB_HEADSET_ANLG : 112 ((state == 2) ? BIT_USB_HEADSET_DGTL : 0))); 113 } else if (name.equals("hdmi")) { 114 switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| 115 BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | 116 ((state == 1) ? BIT_HDMI_AUDIO : 0)); 117 } else { 118 switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| 119 BIT_USB_HEADSET_DGTL)) | 120 ((state == 1) ? BIT_HEADSET : 121 ((state == 2) ? BIT_HEADSET_NO_MIC : 0))); 122 } 123 update(name, switchState); 124 } 125 126 private synchronized final void init() { 127 char[] buffer = new char[1024]; 128 129 String newName = mHeadsetName; 130 int newState = mHeadsetState; 131 mPrevHeadsetState = mHeadsetState; 132 133 if (LOG) Slog.v(TAG, "init()"); 134 135 for (int i = 0; i < MAX_AUDIO_PORTS; i++) { 136 try { 137 FileReader file = new FileReader(uEventInfo[i][1]); 138 int len = file.read(buffer, 0, 1024); 139 file.close(); 140 newState = Integer.valueOf((new String(buffer, 0, len)).trim()); 141 142 file = new FileReader(uEventInfo[i][2]); 143 len = file.read(buffer, 0, 1024); 144 file.close(); 145 newName = new String(buffer, 0, len).trim(); 146 147 if (newState > 0) { 148 updateState(newName, newState); 149 } 150 151 } catch (FileNotFoundException e) { 152 Slog.w(TAG, "This kernel does not have wired headset support"); 153 } catch (Exception e) { 154 Slog.e(TAG, "" , e); 155 } 156 } 157 } 158 159 private synchronized final void update(String newName, int newState) { 160 // Retain only relevant bits 161 int headsetState = newState & SUPPORTED_HEADSETS; 162 int newOrOld = headsetState | mHeadsetState; 163 int delay = 0; 164 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; 165 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; 166 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); 167 boolean h2wStateChange = true; 168 boolean usbStateChange = true; 169 // reject all suspect transitions: only accept state changes from: 170 // - a: 0 heaset to 1 headset 171 // - b: 1 headset to 0 headset 172 if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," 173 + "mHeadsetState = "+mHeadsetState); 174 if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { 175 Log.e(TAG, "unsetting h2w flag"); 176 h2wStateChange = false; 177 } 178 // - c: 0 usb headset to 1 usb headset 179 // - d: 1 usb headset to 0 usb headset 180 if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { 181 Log.e(TAG, "unsetting usb flag"); 182 usbStateChange = false; 183 } 184 if (!h2wStateChange && !usbStateChange) { 185 Log.e(TAG, "invalid transition, returning ..."); 186 return; 187 } 188 189 mHeadsetName = newName; 190 mPrevHeadsetState = mHeadsetState; 191 mHeadsetState = headsetState; 192 193 if (headsetState == 0) { 194 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); 195 mContext.sendBroadcast(intent); 196 // It can take hundreds of ms flush the audio pipeline after 197 // apps pause audio playback, but audio route changes are 198 // immediate, so delay the route change by 1000ms. 199 // This could be improved once the audio sub-system provides an 200 // interface to clear the audio pipeline. 201 delay = 1000; 202 } else { 203 // Insert the same delay for headset connection so that the connection event is not 204 // broadcast before the disconnection event in case of fast removal/insertion 205 if (mHandler.hasMessages(0)) { 206 delay = 1000; 207 } 208 } 209 mWakeLock.acquire(); 210 mHandler.sendMessageDelayed(mHandler.obtainMessage(0, 211 mHeadsetState, 212 mPrevHeadsetState, 213 mHeadsetName), 214 delay); 215 } 216 217 private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { 218 int allHeadsets = SUPPORTED_HEADSETS; 219 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { 220 if ((curHeadset & allHeadsets) != 0) { 221 sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); 222 allHeadsets &= ~curHeadset; 223 } 224 } 225 } 226 227 private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { 228 if ((headsetState & headset) != (prevHeadsetState & headset)) { 229 230 int state = 0; 231 if ((headsetState & headset) != 0) { 232 state = 1; 233 } 234 if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) || 235 (headset == BIT_HDMI_AUDIO)) { 236 Intent intent; 237 238 // Pack up the values and broadcast them to everyone 239 if (headset == BIT_USB_HEADSET_ANLG) { 240 intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG); 241 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 242 intent.putExtra("state", state); 243 intent.putExtra("name", headsetName); 244 ActivityManagerNative.broadcastStickyIntent(intent, null); 245 } else if (headset == BIT_USB_HEADSET_DGTL) { 246 intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG); 247 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 248 intent.putExtra("state", state); 249 intent.putExtra("name", headsetName); 250 ActivityManagerNative.broadcastStickyIntent(intent, null); 251 } else if (headset == BIT_HDMI_AUDIO) { 252 intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG); 253 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 254 intent.putExtra("state", state); 255 intent.putExtra("name", headsetName); 256 ActivityManagerNative.broadcastStickyIntent(intent, null); 257 } 258 259 if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName); 260 // TODO: Should we require a permission? 261 } 262 if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) { 263 264 // Pack up the values and broadcast them to everyone 265 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); 266 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 267 //int state = 0; 268 int microphone = 0; 269 270 if ((headset & HEADSETS_WITH_MIC) != 0) { 271 microphone = 1; 272 } 273 274 intent.putExtra("state", state); 275 intent.putExtra("name", headsetName); 276 intent.putExtra("microphone", microphone); 277 278 if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); 279 // TODO: Should we require a permission? 280 ActivityManagerNative.broadcastStickyIntent(intent, null); 281 } 282 } 283 } 284 285 private final Handler mHandler = new Handler() { 286 @Override 287 public void handleMessage(Message msg) { 288 sendIntents(msg.arg1, msg.arg2, (String)msg.obj); 289 mWakeLock.release(); 290 } 291 }; 292 } 293