blob: bee3108b412b088b965cf27d7c3630db54d66fff [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);
46 private static final int BIT_TTY = (1 << 2);
47 private static final int BIT_FM_HEADSET = (1 << 3);
48 private static final int BIT_FM_SPEAKER = (1 << 4);
49
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 private int mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070051 private int mPrevHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 private String mHeadsetName;
Eric Laurenta553c252009-07-17 12:17:14 -070053 private boolean mPendingIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054
Nick Pelly9ac93212009-04-08 15:09:15 -070055 private final Context mContext;
56 private final WakeLock mWakeLock; // held while there is a pending route change
57
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 public HeadsetObserver(Context context) {
59 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070060 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
61 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
62 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063
64 startObserving(HEADSET_UEVENT_MATCH);
65
66 init(); // set initial status
67 }
68
69 @Override
70 public void onUEvent(UEventObserver.UEvent event) {
Joe Onorato9a5e3e12009-07-01 21:04:03 -040071 if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072
73 try {
74 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
75 } catch (NumberFormatException e) {
76 Log.e(TAG, "Could not parse switch state from event " + event);
77 }
78 }
79
80 private synchronized final void init() {
81 char[] buffer = new char[1024];
82
83 String newName = mHeadsetName;
84 int newState = mHeadsetState;
Eric Laurenta553c252009-07-17 12:17:14 -070085 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 try {
87 FileReader file = new FileReader(HEADSET_STATE_PATH);
88 int len = file.read(buffer, 0, 1024);
89 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
90
91 file = new FileReader(HEADSET_NAME_PATH);
92 len = file.read(buffer, 0, 1024);
93 newName = new String(buffer, 0, len).trim();
94
95 } catch (FileNotFoundException e) {
96 Log.w(TAG, "This kernel does not have wired headset support");
97 } catch (Exception e) {
98 Log.e(TAG, "" , e);
99 }
100
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 update(newName, newState);
102 }
103
104 private synchronized final void update(String newName, int newState) {
105 if (newName != mHeadsetName || newState != mHeadsetState) {
Eric Laurenta553c252009-07-17 12:17:14 -0700106 boolean isUnplug = false;
107 if ( (mHeadsetState & BIT_HEADSET) > 0 || (mHeadsetState & BIT_HEADSET_NO_MIC) > 0) {
108 if ((newState & BIT_HEADSET) == 0 && (newState & BIT_HEADSET_NO_MIC) == 0)
109 isUnplug = true;
110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 mHeadsetName = newName;
Eric Laurenta553c252009-07-17 12:17:14 -0700112 mPrevHeadsetState = mHeadsetState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 mHeadsetState = newState;
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);
138
139 intent.putExtra("state", mHeadsetState);
140 intent.putExtra("name", mHeadsetName);
141
142 // TODO: Should we require a permission?
143 ActivityManagerNative.broadcastStickyIntent(intent, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 }
145
146 private final Handler mHandler = new Handler() {
147 @Override
148 public void handleMessage(Message msg) {
Eric Laurenta553c252009-07-17 12:17:14 -0700149 if (mPendingIntent) {
150 sendIntent();
151 mPendingIntent = false;
152 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700153 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700155 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156
157}