blob: 43de36b31e916c54984f26cf5f1774c2c4ac6e35 [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;
Eric Laurenta553c252009-07-17 12:17:14 -070052 private boolean mPendingIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Nick Pelly9ac93212009-04-08 15:09:15 -070054 private final Context mContext;
55 private final WakeLock mWakeLock; // held while there is a pending route change
56
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 public HeadsetObserver(Context context) {
58 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070059 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
60 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
61 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
63 startObserving(HEADSET_UEVENT_MATCH);
64
65 init(); // set initial status
66 }
67
68 @Override
69 public void onUEvent(UEventObserver.UEvent event) {
Joe Onorato9a5e3e12009-07-01 21:04:03 -040070 if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
72 try {
73 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
74 } catch (NumberFormatException e) {
75 Log.e(TAG, "Could not parse switch state from event " + event);
76 }
77 }
78
79 private synchronized final void init() {
80 char[] buffer = new char[1024];
81
82 String newName = mHeadsetName;
83 int newState = mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070084 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 try {
86 FileReader file = new FileReader(HEADSET_STATE_PATH);
87 int len = file.read(buffer, 0, 1024);
88 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
89
90 file = new FileReader(HEADSET_NAME_PATH);
91 len = file.read(buffer, 0, 1024);
92 newName = new String(buffer, 0, len).trim();
93
94 } catch (FileNotFoundException e) {
95 Log.w(TAG, "This kernel does not have wired headset support");
96 } catch (Exception e) {
97 Log.e(TAG, "" , e);
98 }
99
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 update(newName, newState);
101 }
102
103 private synchronized final void update(String newName, int newState) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800104 // Retain only relevant bits
Eric Laurent2083b292009-11-20 07:26:56 -0800105 int headsetState = newState & SUPPORTED_HEADSETS;
106 int newOrOld = headsetState | mHeadsetState;
107 // 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;
117 mPendingIntent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
Eric Laurent2083b292009-11-20 07:26:56 -0800119 if (headsetState == 0) {
120 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
121 mContext.sendBroadcast(intent);
Eric Laurenta553c252009-07-17 12:17:14 -0700122
Eric Laurent2083b292009-11-20 07:26:56 -0800123 // It can take hundreds of ms flush the audio pipeline after
124 // apps pause audio playback, but audio route changes are
125 // immediate, so delay the route change by 1000ms.
126 // This could be improved once the audio sub-system provides an
127 // interface to clear the audio pipeline.
128 mWakeLock.acquire();
129 mHandler.sendEmptyMessageDelayed(0, 1000);
130 } else {
131 sendIntents();
132 mPendingIntent = false;
133 }
134 }
135
136 private synchronized final void sendIntents() {
137 int allHeadsets = SUPPORTED_HEADSETS;
138 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
139 if ((curHeadset & allHeadsets) != 0) {
140 sendIntent(curHeadset);
141 allHeadsets &= ~curHeadset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 }
143 }
144 }
145
Eric Laurent2083b292009-11-20 07:26:56 -0800146 private final void sendIntent(int headset) {
147 if ((mHeadsetState & headset) != (mPrevHeadsetState & headset)) {
148 // Pack up the values and broadcast them to everyone
149 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
150 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
151 int state = 0;
152 int microphone = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153
Eric Laurent2083b292009-11-20 07:26:56 -0800154 if ((headset & HEADSETS_WITH_MIC) != 0) {
155 microphone = 1;
156 }
157 if ((mHeadsetState & headset) != 0) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800158 state = 1;
159 }
Eric Laurent2083b292009-11-20 07:26:56 -0800160 intent.putExtra("state", state);
161 intent.putExtra("name", mHeadsetName);
162 intent.putExtra("microphone", microphone);
163
164 if (LOG) Log.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+mHeadsetName+" mic: "+microphone);
165 // TODO: Should we require a permission?
166 ActivityManagerNative.broadcastStickyIntent(intent, null);
Eric Laurent923d7d72009-11-12 12:09:06 -0800167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 }
169
170 private final Handler mHandler = new Handler() {
171 @Override
172 public void handleMessage(Message msg) {
Eric Laurenta553c252009-07-17 12:17:14 -0700173 if (mPendingIntent) {
Eric Laurent2083b292009-11-20 07:26:56 -0800174 sendIntents();
Eric Laurenta553c252009-07-17 12:17:14 -0700175 mPendingIntent = false;
176 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700177 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700179 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180
181}