blob: 6f0a91d8a30a1a0e27a52d6d11b4c580e2262558 [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;
Joe Onorato8a9b2202010-02-26 18:56:32 -080027import android.util.Slog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 public HeadsetObserver(Context context) {
57 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070058 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
59 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
60 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
62 startObserving(HEADSET_UEVENT_MATCH);
63
64 init(); // set initial status
65 }
66
67 @Override
68 public void onUEvent(UEventObserver.UEvent event) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080069 if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
71 try {
72 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
73 } catch (NumberFormatException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080074 Slog.e(TAG, "Could not parse switch state from event " + event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 }
76 }
77
78 private synchronized final void init() {
79 char[] buffer = new char[1024];
80
81 String newName = mHeadsetName;
82 int newState = mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070083 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 try {
85 FileReader file = new FileReader(HEADSET_STATE_PATH);
86 int len = file.read(buffer, 0, 1024);
87 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
88
89 file = new FileReader(HEADSET_NAME_PATH);
90 len = file.read(buffer, 0, 1024);
91 newName = new String(buffer, 0, len).trim();
92
93 } catch (FileNotFoundException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080094 Slog.w(TAG, "This kernel does not have wired headset support");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080096 Slog.e(TAG, "" , e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 }
98
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 update(newName, newState);
100 }
101
102 private synchronized final void update(String newName, int newState) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800103 // Retain only relevant bits
Eric Laurent2083b292009-11-20 07:26:56 -0800104 int headsetState = newState & SUPPORTED_HEADSETS;
105 int newOrOld = headsetState | mHeadsetState;
Eric Laurent700aab672010-01-22 07:50:58 -0800106 int delay = 0;
Eric Laurent2083b292009-11-20 07:26:56 -0800107 // reject all suspect transitions: only accept state changes from:
108 // - a: 0 heaset to 1 headset
109 // - b: 1 headset to 0 headset
110 if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) {
111 return;
112 }
Eric Laurent923d7d72009-11-12 12:09:06 -0800113
Eric Laurent2083b292009-11-20 07:26:56 -0800114 mHeadsetName = newName;
115 mPrevHeadsetState = mHeadsetState;
116 mHeadsetState = headsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117
Eric Laurent2083b292009-11-20 07:26:56 -0800118 if (headsetState == 0) {
119 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
120 mContext.sendBroadcast(intent);
Eric Laurent2083b292009-11-20 07:26:56 -0800121 // It can take hundreds of ms flush the audio pipeline after
122 // apps pause audio playback, but audio route changes are
123 // immediate, so delay the route change by 1000ms.
124 // This could be improved once the audio sub-system provides an
125 // interface to clear the audio pipeline.
Eric Laurent700aab672010-01-22 07:50:58 -0800126 delay = 1000;
Eric Laurent2083b292009-11-20 07:26:56 -0800127 } else {
Eric Laurent700aab672010-01-22 07:50:58 -0800128 // Insert the same delay for headset connection so that the connection event is not
129 // broadcast before the disconnection event in case of fast removal/insertion
130 if (mHandler.hasMessages(0)) {
131 delay = 1000;
132 }
Eric Laurent2083b292009-11-20 07:26:56 -0800133 }
Eric Laurent700aab672010-01-22 07:50:58 -0800134 mWakeLock.acquire();
135 mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
136 mHeadsetState,
137 mPrevHeadsetState,
138 mHeadsetName),
139 delay);
Eric Laurent2083b292009-11-20 07:26:56 -0800140 }
141
Eric Laurentda4cc342009-12-14 03:45:41 -0800142 private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
Eric Laurent2083b292009-11-20 07:26:56 -0800143 int allHeadsets = SUPPORTED_HEADSETS;
144 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
145 if ((curHeadset & allHeadsets) != 0) {
Eric Laurentda4cc342009-12-14 03:45:41 -0800146 sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
Eric Laurent2083b292009-11-20 07:26:56 -0800147 allHeadsets &= ~curHeadset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 }
149 }
150 }
151
Eric Laurentda4cc342009-12-14 03:45:41 -0800152 private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
153 if ((headsetState & headset) != (prevHeadsetState & headset)) {
Eric Laurent2083b292009-11-20 07:26:56 -0800154 // Pack up the values and broadcast them to everyone
155 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
156 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
157 int state = 0;
158 int microphone = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159
Eric Laurent2083b292009-11-20 07:26:56 -0800160 if ((headset & HEADSETS_WITH_MIC) != 0) {
161 microphone = 1;
162 }
Eric Laurentda4cc342009-12-14 03:45:41 -0800163 if ((headsetState & headset) != 0) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800164 state = 1;
165 }
Eric Laurent2083b292009-11-20 07:26:56 -0800166 intent.putExtra("state", state);
Eric Laurentda4cc342009-12-14 03:45:41 -0800167 intent.putExtra("name", headsetName);
Eric Laurent2083b292009-11-20 07:26:56 -0800168 intent.putExtra("microphone", microphone);
169
Joe Onorato8a9b2202010-02-26 18:56:32 -0800170 if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
Eric Laurent2083b292009-11-20 07:26:56 -0800171 // TODO: Should we require a permission?
172 ActivityManagerNative.broadcastStickyIntent(intent, null);
Eric Laurent923d7d72009-11-12 12:09:06 -0800173 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 }
175
176 private final Handler mHandler = new Handler() {
177 @Override
178 public void handleMessage(Message msg) {
Eric Laurentda4cc342009-12-14 03:45:41 -0800179 sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
Nick Pelly9ac93212009-04-08 15:09:15 -0700180 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700182 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183}