blob: 3fc1e0ea58794f0853d5c97539276da7d1e350a3 [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();
38
39 private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
40 private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
41 private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
42
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 private int mHeadsetState;
44 private String mHeadsetName;
45 private boolean mAudioRouteNeedsUpdate;
46 private AudioManager mAudioManager;
47
Nick Pelly9ac93212009-04-08 15:09:15 -070048 private final Context mContext;
49 private final WakeLock mWakeLock; // held while there is a pending route change
50
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 public HeadsetObserver(Context context) {
52 mContext = context;
Nick Pelly9ac93212009-04-08 15:09:15 -070053 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
54 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
55 mWakeLock.setReferenceCounted(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
57 startObserving(HEADSET_UEVENT_MATCH);
58
59 init(); // set initial status
60 }
61
62 @Override
63 public void onUEvent(UEventObserver.UEvent event) {
64 Log.v(TAG, "Headset UEVENT: " + event.toString());
65
66 try {
67 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
68 } catch (NumberFormatException e) {
69 Log.e(TAG, "Could not parse switch state from event " + event);
70 }
71 }
72
73 private synchronized final void init() {
74 char[] buffer = new char[1024];
75
76 String newName = mHeadsetName;
77 int newState = mHeadsetState;
78 try {
79 FileReader file = new FileReader(HEADSET_STATE_PATH);
80 int len = file.read(buffer, 0, 1024);
81 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
82
83 file = new FileReader(HEADSET_NAME_PATH);
84 len = file.read(buffer, 0, 1024);
85 newName = new String(buffer, 0, len).trim();
86
87 } catch (FileNotFoundException e) {
88 Log.w(TAG, "This kernel does not have wired headset support");
89 } catch (Exception e) {
90 Log.e(TAG, "" , e);
91 }
92
93 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
94 update(newName, newState);
95 }
96
97 private synchronized final void update(String newName, int newState) {
98 if (newName != mHeadsetName || newState != mHeadsetState) {
99 boolean isUnplug = (newState == 0 && mHeadsetState == 1);
100 mHeadsetName = newName;
101 mHeadsetState = newState;
102 mAudioRouteNeedsUpdate = true;
103
104 sendIntent(isUnplug);
105
106 if (isUnplug) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700107 // It can take hundreds of ms flush the audio pipeline after
108 // apps pause audio playback, but audio route changes are
109 // immediate, so delay the route change by 1000ms.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 // This could be improved once the audio sub-system provides an
111 // interface to clear the audio pipeline.
Nick Pelly9ac93212009-04-08 15:09:15 -0700112 mWakeLock.acquire();
The Android Open Source Project10592532009-03-18 17:39:46 -0700113 mHandler.sendEmptyMessageDelayed(0, 1000);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 } else {
115 updateAudioRoute();
116 }
117 }
118 }
119
120 private synchronized final void sendIntent(boolean isUnplug) {
121 // Pack up the values and broadcast them to everyone
122 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
123 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
124
125 intent.putExtra("state", mHeadsetState);
126 intent.putExtra("name", mHeadsetName);
127
128 // TODO: Should we require a permission?
129 ActivityManagerNative.broadcastStickyIntent(intent, null);
130
131 if (isUnplug) {
132 intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
133 mContext.sendBroadcast(intent);
134 }
135 }
136
137 private synchronized final void updateAudioRoute() {
138 if (mAudioRouteNeedsUpdate) {
139 mAudioManager.setWiredHeadsetOn(mHeadsetState == 1);
140 mAudioRouteNeedsUpdate = false;
141 }
142 }
143
144 private final Handler mHandler = new Handler() {
145 @Override
146 public void handleMessage(Message msg) {
147 updateAudioRoute();
Nick Pelly9ac93212009-04-08 15:09:15 -0700148 mWakeLock.release();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 }
Nick Pelly9ac93212009-04-08 15:09:15 -0700150 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151
152}