blob: 58fa69e888a9eeb01f1cdea974a104905bc57542 [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();
Joe Onorato9a5e3e12009-07-01 21:04:03 -040038 private static final boolean LOG = false;
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 Laurenta553c252009-07-17 12:17:14 -070046
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 private int mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070048 private int mPrevHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 private String mHeadsetName;
Eric Laurenta553c252009-07-17 12:17:14 -070050 private boolean mPendingIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
Nick Pelly9ac93212009-04-08 15:09:15 -070052 private final Context mContext;
53 private final WakeLock mWakeLock; // held while there is a pending route change
54
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 public HeadsetObserver(Context context) {
56 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070057 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
58 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
59 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
61 startObserving(HEADSET_UEVENT_MATCH);
62
63 init(); // set initial status
64 }
65
66 @Override
67 public void onUEvent(UEventObserver.UEvent event) {
Joe Onorato9a5e3e12009-07-01 21:04:03 -040068 if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
70 try {
71 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
72 } catch (NumberFormatException e) {
73 Log.e(TAG, "Could not parse switch state from event " + event);
74 }
75 }
76
77 private synchronized final void init() {
78 char[] buffer = new char[1024];
79
80 String newName = mHeadsetName;
81 int newState = mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070082 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 try {
84 FileReader file = new FileReader(HEADSET_STATE_PATH);
85 int len = file.read(buffer, 0, 1024);
86 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
87
88 file = new FileReader(HEADSET_NAME_PATH);
89 len = file.read(buffer, 0, 1024);
90 newName = new String(buffer, 0, len).trim();
91
92 } catch (FileNotFoundException e) {
93 Log.w(TAG, "This kernel does not have wired headset support");
94 } catch (Exception e) {
95 Log.e(TAG, "" , e);
96 }
97
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 update(newName, newState);
99 }
100
101 private synchronized final void update(String newName, int newState) {
Eric Laurent923d7d72009-11-12 12:09:06 -0800102 // Retain only relevant bits
103 int headsetState = newState & (BIT_HEADSET|BIT_HEADSET_NO_MIC);
104
105 if (headsetState != mHeadsetState) {
Eric Laurenta553c252009-07-17 12:17:14 -0700106 boolean isUnplug = false;
Eric Laurent923d7d72009-11-12 12:09:06 -0800107 if (((mHeadsetState & BIT_HEADSET) != 0 && (headsetState & BIT_HEADSET) == 0) ||
108 ((mHeadsetState & BIT_HEADSET_NO_MIC) != 0 && (headsetState & BIT_HEADSET_NO_MIC) == 0)) {
109 isUnplug = true;
Eric Laurenta553c252009-07-17 12:17:14 -0700110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 mHeadsetName = newName;
Eric Laurenta553c252009-07-17 12:17:14 -0700112 mPrevHeadsetState = mHeadsetState;
Eric Laurent923d7d72009-11-12 12:09:06 -0800113 mHeadsetState = headsetState;
Eric Laurenta553c252009-07-17 12:17:14 -0700114 mPendingIntent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115
116 if (isUnplug) {
Eric Laurenta553c252009-07-17 12:17:14 -0700117 Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
118 mContext.sendBroadcast(intent);
119
The Android Open Source Project10592532009-03-18 17:39:46 -0700120 // It can take hundreds of ms flush the audio pipeline after
121 // apps pause audio playback, but audio route changes are
122 // immediate, so delay the route change by 1000ms.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 // This could be improved once the audio sub-system provides an
124 // interface to clear the audio pipeline.
Nick Pelly9ac93212009-04-08 15:09:15 -0700125 mWakeLock.acquire();
The Android Open Source Project10592532009-03-18 17:39:46 -0700126 mHandler.sendEmptyMessageDelayed(0, 1000);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 } else {
Eric Laurenta553c252009-07-17 12:17:14 -0700128 sendIntent();
129 mPendingIntent = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 }
131 }
132 }
133
Eric Laurenta553c252009-07-17 12:17:14 -0700134 private synchronized final void sendIntent() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 // Pack up the values and broadcast them to everyone
136 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
137 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
Eric Laurent923d7d72009-11-12 12:09:06 -0800138 int state = 0;
139 int microphone = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140
Eric Laurent923d7d72009-11-12 12:09:06 -0800141 if ((mHeadsetState & BIT_HEADSET) != (mPrevHeadsetState & BIT_HEADSET)) {
142 microphone = 1;
143 if ((mHeadsetState & BIT_HEADSET) != 0) {
144 state = 1;
145 }
146 } else if ((mHeadsetState & BIT_HEADSET_NO_MIC) != (mPrevHeadsetState & BIT_HEADSET_NO_MIC)) {
147 if ((mHeadsetState & BIT_HEADSET_NO_MIC) != 0) {
148 state = 1;
149 }
150 }
151
152 intent.putExtra("state", state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 intent.putExtra("name", mHeadsetName);
Eric Laurent923d7d72009-11-12 12:09:06 -0800154 intent.putExtra("microphone", microphone);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155
156 // TODO: Should we require a permission?
157 ActivityManagerNative.broadcastStickyIntent(intent, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 }
159
160 private final Handler mHandler = new Handler() {
161 @Override
162 public void handleMessage(Message msg) {
Eric Laurenta553c252009-07-17 12:17:14 -0700163 if (mPendingIntent) {
164 sendIntent();
165 mPendingIntent = false;
166 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700167 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700169 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170
171}