Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 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; |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 20 | import android.content.BroadcastReceiver; |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 21 | import android.content.Context; |
| 22 | import android.content.Intent; |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 23 | import android.content.IntentFilter; |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 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; |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 42 | private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 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", |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 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"} }; |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 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); |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 57 | private static final int BIT_HDMI_AUDIO = (1 << 4); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 58 | private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 59 | BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| |
| 60 | BIT_HDMI_AUDIO); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 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 | |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 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 |
Elliott Hughes | b55dcc2 | 2010-11-04 11:55:47 -0700 | [diff] [blame] | 88 | for (int i = 0; i < MAX_AUDIO_PORTS; i++) { |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 89 | startObserving(uEventInfo[i][0]); |
| 90 | } |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 91 | } |
| 92 | } |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 93 | |
| 94 | @Override |
| 95 | public void onUEvent(UEventObserver.UEvent event) { |
| 96 | if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); |
| 97 | |
| 98 | try { |
Praveen Bharathi | 7526da4 | 2010-11-16 02:08:02 -0600 | [diff] [blame] | 99 | String name = event.get("SWITCH_NAME"); |
| 100 | int state = Integer.parseInt(event.get("SWITCH_STATE")); |
| 101 | updateState(name, state); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 102 | } catch (NumberFormatException e) { |
| 103 | Slog.e(TAG, "Could not parse switch state from event " + event); |
| 104 | } |
| 105 | } |
| 106 | |
Praveen Bharathi | 7526da4 | 2010-11-16 02:08:02 -0600 | [diff] [blame] | 107 | private synchronized final void updateState(String name, int state) |
| 108 | { |
| 109 | if (name.equals("usb_audio")) { |
| 110 | if (state == 1) { |
| 111 | switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| |
| 112 | BIT_USB_HEADSET_DGTL|BIT_HDMI_AUDIO)) | |
| 113 | (state << 2)); |
| 114 | } else if (state == 2) { |
| 115 | switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| |
| 116 | BIT_USB_HEADSET_ANLG|BIT_HDMI_AUDIO)) | |
| 117 | (state << 3)); |
| 118 | } else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)); |
| 119 | } |
| 120 | else if (name.equals("hdmi")) { |
| 121 | switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| |
| 122 | BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | |
| 123 | (state << 4)); |
| 124 | } |
| 125 | else { |
| 126 | switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| |
| 127 | BIT_USB_HEADSET_DGTL)) | |
| 128 | state); |
| 129 | } |
| 130 | update(name, switchState); |
| 131 | } |
| 132 | |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 133 | private synchronized final void init() { |
| 134 | char[] buffer = new char[1024]; |
| 135 | |
| 136 | String newName = mHeadsetName; |
| 137 | int newState = mHeadsetState; |
| 138 | mPrevHeadsetState = mHeadsetState; |
| 139 | |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 140 | if (LOG) Slog.v(TAG, "init()"); |
| 141 | |
Elliott Hughes | b55dcc2 | 2010-11-04 11:55:47 -0700 | [diff] [blame] | 142 | for (int i = 0; i < MAX_AUDIO_PORTS; i++) { |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 143 | try { |
| 144 | FileReader file = new FileReader(uEventInfo[i][1]); |
| 145 | int len = file.read(buffer, 0, 1024); |
Brian Carlstrom | 237171f | 2010-11-04 16:23:21 -0700 | [diff] [blame] | 146 | file.close(); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 147 | newState = Integer.valueOf((new String(buffer, 0, len)).trim()); |
| 148 | |
| 149 | file = new FileReader(uEventInfo[i][2]); |
| 150 | len = file.read(buffer, 0, 1024); |
Brian Carlstrom | 237171f | 2010-11-04 16:23:21 -0700 | [diff] [blame] | 151 | file.close(); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 152 | newName = new String(buffer, 0, len).trim(); |
| 153 | |
Praveen Bharathi | 7526da4 | 2010-11-16 02:08:02 -0600 | [diff] [blame] | 154 | if (newState > 0) { |
| 155 | updateState(newName, newState); |
| 156 | } |
| 157 | |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 158 | } catch (FileNotFoundException e) { |
| 159 | Slog.w(TAG, "This kernel does not have wired headset support"); |
| 160 | } catch (Exception e) { |
| 161 | Slog.e(TAG, "" , e); |
| 162 | } |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 163 | } |
| 164 | } |
| 165 | |
| 166 | private synchronized final void update(String newName, int newState) { |
| 167 | // Retain only relevant bits |
| 168 | int headsetState = newState & SUPPORTED_HEADSETS; |
| 169 | int newOrOld = headsetState | mHeadsetState; |
| 170 | int delay = 0; |
| 171 | int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; |
| 172 | int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; |
| 173 | int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); |
| 174 | boolean h2wStateChange = true; |
| 175 | boolean usbStateChange = true; |
| 176 | // reject all suspect transitions: only accept state changes from: |
| 177 | // - a: 0 heaset to 1 headset |
| 178 | // - b: 1 headset to 0 headset |
Eric Laurent | 3fc78a5 | 2010-11-18 15:50:22 -0800 | [diff] [blame] | 179 | if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+"," |
| 180 | + "mHeadsetState = "+mHeadsetState); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 181 | if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) { |
| 182 | Log.e(TAG, "unsetting h2w flag"); |
| 183 | h2wStateChange = false; |
| 184 | } |
| 185 | // - c: 0 usb headset to 1 usb headset |
| 186 | // - d: 1 usb headset to 0 usb headset |
| 187 | if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) { |
| 188 | Log.e(TAG, "unsetting usb flag"); |
| 189 | usbStateChange = false; |
| 190 | } |
| 191 | if (!h2wStateChange && !usbStateChange) { |
| 192 | Log.e(TAG, "invalid transition, returning ..."); |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | mHeadsetName = newName; |
| 197 | mPrevHeadsetState = mHeadsetState; |
| 198 | mHeadsetState = headsetState; |
| 199 | |
| 200 | if (headsetState == 0) { |
| 201 | Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); |
| 202 | mContext.sendBroadcast(intent); |
| 203 | // It can take hundreds of ms flush the audio pipeline after |
| 204 | // apps pause audio playback, but audio route changes are |
| 205 | // immediate, so delay the route change by 1000ms. |
| 206 | // This could be improved once the audio sub-system provides an |
| 207 | // interface to clear the audio pipeline. |
| 208 | delay = 1000; |
| 209 | } else { |
| 210 | // Insert the same delay for headset connection so that the connection event is not |
| 211 | // broadcast before the disconnection event in case of fast removal/insertion |
| 212 | if (mHandler.hasMessages(0)) { |
| 213 | delay = 1000; |
| 214 | } |
| 215 | } |
| 216 | mWakeLock.acquire(); |
| 217 | mHandler.sendMessageDelayed(mHandler.obtainMessage(0, |
| 218 | mHeadsetState, |
| 219 | mPrevHeadsetState, |
| 220 | mHeadsetName), |
| 221 | delay); |
| 222 | } |
| 223 | |
| 224 | private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) { |
| 225 | int allHeadsets = SUPPORTED_HEADSETS; |
| 226 | for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { |
| 227 | if ((curHeadset & allHeadsets) != 0) { |
| 228 | sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName); |
| 229 | allHeadsets &= ~curHeadset; |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) { |
| 235 | if ((headsetState & headset) != (prevHeadsetState & headset)) { |
| 236 | |
| 237 | int state = 0; |
| 238 | if ((headsetState & headset) != 0) { |
| 239 | state = 1; |
| 240 | } |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 241 | if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) || |
| 242 | (headset == BIT_HDMI_AUDIO)) { |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 243 | Intent intent; |
| 244 | |
| 245 | // Pack up the values and broadcast them to everyone |
| 246 | if (headset == BIT_USB_HEADSET_ANLG) { |
| 247 | intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG); |
| 248 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| 249 | intent.putExtra("state", state); |
| 250 | intent.putExtra("name", headsetName); |
| 251 | ActivityManagerNative.broadcastStickyIntent(intent, null); |
| 252 | } else if (headset == BIT_USB_HEADSET_DGTL) { |
| 253 | intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG); |
| 254 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| 255 | intent.putExtra("state", state); |
| 256 | intent.putExtra("name", headsetName); |
| 257 | ActivityManagerNative.broadcastStickyIntent(intent, null); |
Praveen Bharathi | 26e3734 | 2010-11-02 19:23:30 -0700 | [diff] [blame] | 258 | } else if (headset == BIT_HDMI_AUDIO) { |
| 259 | intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG); |
| 260 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| 261 | intent.putExtra("state", state); |
| 262 | intent.putExtra("name", headsetName); |
| 263 | ActivityManagerNative.broadcastStickyIntent(intent, null); |
Praveen Bharathi | 21e941b | 2010-10-06 15:23:14 -0500 | [diff] [blame] | 264 | } |
| 265 | |
| 266 | if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName); |
| 267 | // TODO: Should we require a permission? |
| 268 | } |
| 269 | if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) { |
| 270 | |
| 271 | // Pack up the values and broadcast them to everyone |
| 272 | Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG); |
| 273 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| 274 | //int state = 0; |
| 275 | int microphone = 0; |
| 276 | |
| 277 | if ((headset & HEADSETS_WITH_MIC) != 0) { |
| 278 | microphone = 1; |
| 279 | } |
| 280 | |
| 281 | intent.putExtra("state", state); |
| 282 | intent.putExtra("name", headsetName); |
| 283 | intent.putExtra("microphone", microphone); |
| 284 | |
| 285 | if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone); |
| 286 | // TODO: Should we require a permission? |
| 287 | ActivityManagerNative.broadcastStickyIntent(intent, null); |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | private final Handler mHandler = new Handler() { |
| 293 | @Override |
| 294 | public void handleMessage(Message msg) { |
| 295 | sendIntents(msg.arg1, msg.arg2, (String)msg.obj); |
| 296 | mWakeLock.release(); |
| 297 | } |
| 298 | }; |
| 299 | } |