blob: c94450b17b8a4a9a19dbaa77a0cd7c50aaa167e2 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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.app.ActivityManagerNative;
20import android.content.Context;
21import android.content.Intent;
22import android.os.Handler;
23import android.os.Message;
Nick Pelly9ac93212009-04-08 15:09:15 -070024import android.os.PowerManager;
25import android.os.PowerManager.WakeLock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.os.UEventObserver;
27import android.util.Log;
28import android.media.AudioManager;
29
30import java.io.FileReader;
31import java.io.FileNotFoundException;
32
33/**
34 * <p>HeadsetObserver monitors for a wired headset.
35 */
36class HeadsetObserver extends UEventObserver {
37 private static final String TAG = HeadsetObserver.class.getSimpleName();
Eric Olsene7096eb2009-11-23 13:06:07 -080038 private static final boolean LOG = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
40 private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
41 private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
42 private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
43
Eric Laurenta553c252009-07-17 12:17:14 -070044 private static final int BIT_HEADSET = (1 << 0);
45 private static final int BIT_HEADSET_NO_MIC = (1 << 1);
Eric Laurent2083b292009-11-20 07:26:56 -080046 private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC);
47 private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
Eric Laurenta553c252009-07-17 12:17:14 -070048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 private int mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070050 private int mPrevHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 private String mHeadsetName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
Nick Pelly9ac93212009-04-08 15:09:15 -070053 private final Context mContext;
54 private final WakeLock mWakeLock; // held while there is a pending route change
55
Eric Laurentaead64d2010-02-02 09:42:33 -080056 private boolean mHandleTTY;
57 private int mTTYState;
58 private AudioManager mAudioManager = null;
59
60 // special use of bits in headset state received from kernel made by some
61 // platforms to indicate changes in TTY mode.
62 private static final int BIT_TTY_OFF = 0;
63 private static final int BIT_TTY_FULL = (1 << 2);
64 private static final int BIT_TTY_VCO = (1 << 5);
65 private static final int BIT_TTY_HCO = (1 << 6);
66 private static final int TTY_BITS_MASK = (BIT_TTY_FULL | BIT_TTY_VCO | BIT_TTY_HCO);
67
68
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 public HeadsetObserver(Context context) {
70 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070071 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
72 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
73 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074
75 startObserving(HEADSET_UEVENT_MATCH);
76
Eric Laurentaead64d2010-02-02 09:42:33 -080077 // read settings for TTY mode indication method
78 mHandleTTY = context.getResources().getBoolean(
79 com.android.internal.R.bool.tty_mode_uses_headset_events);
80 mTTYState = BIT_TTY_OFF;
81
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 init(); // set initial status
83 }
84
85 @Override
86 public void onUEvent(UEventObserver.UEvent event) {
Joe Onorato9a5e3e12009-07-01 21:04:03 -040087 if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
89 try {
90 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
91 } catch (NumberFormatException e) {
92 Log.e(TAG, "Could not parse switch state from event " + event);
93 }
94 }
95
96 private synchronized final void init() {
97 char[] buffer = new char[1024];
98
99 String newName = mHeadsetName;
100 int newState = mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -0700101 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 try {
103 FileReader file = new FileReader(HEADSET_STATE_PATH);
104 int len = file.read(buffer, 0, 1024);
105 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
106
107 file = new FileReader(HEADSET_NAME_PATH);
108 len = file.read(buffer, 0, 1024);
109 newName = new String(buffer, 0, len).trim();
110
111 } catch (FileNotFoundException e) {
112 Log.w(TAG, "This kernel does not have wired headset support");
113 } catch (Exception e) {
114 Log.e(TAG, "" , e);
115 }
116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 update(newName, newState);
118 }
119
120 private synchronized final void update(String newName, int newState) {
Eric Laurentaead64d2010-02-02 09:42:33 -0800121 // handle TTY state change first
122 if (mHandleTTY) {
123 int ttyState = newState & TTY_BITS_MASK;
124 if (ttyState != mTTYState) {
125 String ttyMode;
126
127 switch (ttyState) {
128 case BIT_TTY_FULL:
129 ttyMode = "tty_full";
130 break;
131 case BIT_TTY_VCO:
132 ttyMode = "tty_vco";
133 break;
134 case BIT_TTY_HCO:
135 ttyMode = "tty_hco";
136 break;
137 case BIT_TTY_OFF:
138 ttyMode = "tty_off";
139 break;
140 default:
141 ttyMode = "tty_invalid";
142 break;
143
144 }
145 if (ttyMode != "tty_invalid") {
146 mTTYState = ttyState;
147 if (mAudioManager == null) {
148 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
149 }
150 mAudioManager.setParameters("tty_mode="+ttyMode);
151 }
152 }
153 }
Eric Laurent923d7d72009-11-12 12:09:06 -0800154 // Retain only relevant bits
Eric Laurent2083b292009-11-20 07:26:56 -0800155 int headsetState = newState & SUPPORTED_HEADSETS;
156 int newOrOld = headsetState | mHeadsetState;
Eric Laurent700aab672010-01-22 07:50:58 -0800157 int delay = 0;
Eric Laurent2083b292009-11-20 07:26:56 -0800158 // reject all suspect transitions: only accept state changes from:
159 // - a: 0 heaset to 1 headset
160 // - b: 1 headset to 0 headset
161 if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) {
162 return;
163 }
Eric Laurent923d7d72009-11-12 12:09:06 -0800164
Eric Laurent2083b292009-11-20 07:26:56 -0800165 mHeadsetName = newName;
166 mPrevHeadsetState = mHeadsetState;
167 mHeadsetState = headsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168
Eric Laurent2083b292009-11-20 07:26:56 -0800169 if (headsetState == 0) {
170 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
171 mContext.sendBroadcast(intent);
Eric Laurent2083b292009-11-20 07:26:56 -0800172 // It can take hundreds of ms flush the audio pipeline after
173 // apps pause audio playback, but audio route changes are
174 // immediate, so delay the route change by 1000ms.
175 // This could be improved once the audio sub-system provides an
176 // interface to clear the audio pipeline.
Eric Laurent700aab672010-01-22 07:50:58 -0800177 delay = 1000;
Eric Laurent2083b292009-11-20 07:26:56 -0800178 } else {
Eric Laurent700aab672010-01-22 07:50:58 -0800179 // Insert the same delay for headset connection so that the connection event is not
180 // broadcast before the disconnection event in case of fast removal/insertion
181 if (mHandler.hasMessages(0)) {
182 delay = 1000;
183 }
Eric Laurent2083b292009-11-20 07:26:56 -0800184 }
Eric Laurent700aab672010-01-22 07:50:58 -0800185 mWakeLock.acquire();
186 mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
187 mHeadsetState,
188 mPrevHeadsetState,
189 mHeadsetName),
190 delay);
Eric Laurent2083b292009-11-20 07:26:56 -0800191 }
192
Eric Laurentda4cc342009-12-14 03:45:41 -0800193 private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
Eric Laurent2083b292009-11-20 07:26:56 -0800194 int allHeadsets = SUPPORTED_HEADSETS;
195 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
196 if ((curHeadset & allHeadsets) != 0) {
Eric Laurentda4cc342009-12-14 03:45:41 -0800197 sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
Eric Laurent2083b292009-11-20 07:26:56 -0800198 allHeadsets &= ~curHeadset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 }
200 }
201 }
202
Eric Laurentda4cc342009-12-14 03:45:41 -0800203 private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
204 if ((headsetState & headset) != (prevHeadsetState & headset)) {
Eric Laurent2083b292009-11-20 07:26:56 -0800205 // Pack up the values and broadcast them to everyone
206 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
207 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
208 int state = 0;
209 int microphone = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210
Eric Laurent2083b292009-11-20 07:26:56 -0800211 if ((headset & HEADSETS_WITH_MIC) != 0) {
212 microphone = 1;
213 }
Eric Laurentda4cc342009-12-14 03:45:41 -0800214 if ((headsetState & headset) != 0) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800215 state = 1;
216 }
Eric Laurent2083b292009-11-20 07:26:56 -0800217 intent.putExtra("state", state);
Eric Laurentda4cc342009-12-14 03:45:41 -0800218 intent.putExtra("name", headsetName);
Eric Laurent2083b292009-11-20 07:26:56 -0800219 intent.putExtra("microphone", microphone);
220
Eric Laurentda4cc342009-12-14 03:45:41 -0800221 if (LOG) Log.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
Eric Laurent2083b292009-11-20 07:26:56 -0800222 // TODO: Should we require a permission?
223 ActivityManagerNative.broadcastStickyIntent(intent, null);
Eric Laurent923d7d72009-11-12 12:09:06 -0800224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 }
226
227 private final Handler mHandler = new Handler() {
228 @Override
229 public void handleMessage(Message msg) {
Eric Laurentda4cc342009-12-14 03:45:41 -0800230 sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
Nick Pelly9ac93212009-04-08 15:09:15 -0700231 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700233 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234}