blob: 855734dcb80e571a52a4cd7eb01232092773f27e [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;
24import android.os.UEventObserver;
25import android.util.Log;
26import android.media.AudioManager;
27
28import java.io.FileReader;
29import java.io.FileNotFoundException;
30
31/**
32 * <p>HeadsetObserver monitors for a wired headset.
33 */
34class HeadsetObserver extends UEventObserver {
35 private static final String TAG = HeadsetObserver.class.getSimpleName();
36
37 private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
38 private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
39 private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
40
41 private Context mContext;
42
43 private int mHeadsetState;
44 private String mHeadsetName;
45 private boolean mAudioRouteNeedsUpdate;
46 private AudioManager mAudioManager;
47
48 public HeadsetObserver(Context context) {
49 mContext = context;
50
51 startObserving(HEADSET_UEVENT_MATCH);
52
53 init(); // set initial status
54 }
55
56 @Override
57 public void onUEvent(UEventObserver.UEvent event) {
58 Log.v(TAG, "Headset UEVENT: " + event.toString());
59
60 try {
61 update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
62 } catch (NumberFormatException e) {
63 Log.e(TAG, "Could not parse switch state from event " + event);
64 }
65 }
66
67 private synchronized final void init() {
68 char[] buffer = new char[1024];
69
70 String newName = mHeadsetName;
71 int newState = mHeadsetState;
72 try {
73 FileReader file = new FileReader(HEADSET_STATE_PATH);
74 int len = file.read(buffer, 0, 1024);
75 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
76
77 file = new FileReader(HEADSET_NAME_PATH);
78 len = file.read(buffer, 0, 1024);
79 newName = new String(buffer, 0, len).trim();
80
81 } catch (FileNotFoundException e) {
82 Log.w(TAG, "This kernel does not have wired headset support");
83 } catch (Exception e) {
84 Log.e(TAG, "" , e);
85 }
86
87 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
88 update(newName, newState);
89 }
90
91 private synchronized final void update(String newName, int newState) {
92 if (newName != mHeadsetName || newState != mHeadsetState) {
93 boolean isUnplug = (newState == 0 && mHeadsetState == 1);
94 mHeadsetName = newName;
95 mHeadsetState = newState;
96 mAudioRouteNeedsUpdate = true;
97
98 sendIntent(isUnplug);
99
100 if (isUnplug) {
101 // It often takes >200ms to flush the audio pipeline after apps
102 // pause audio playback, but audio route changes are immediate,
103 // so delay the route change by 400ms.
104 // This could be improved once the audio sub-system provides an
105 // interface to clear the audio pipeline.
106 mHandler.sendEmptyMessageDelayed(0, 400);
107 } else {
108 updateAudioRoute();
109 }
110 }
111 }
112
113 private synchronized final void sendIntent(boolean isUnplug) {
114 // Pack up the values and broadcast them to everyone
115 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
116 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
117
118 intent.putExtra("state", mHeadsetState);
119 intent.putExtra("name", mHeadsetName);
120
121 // TODO: Should we require a permission?
122 ActivityManagerNative.broadcastStickyIntent(intent, null);
123
124 if (isUnplug) {
125 intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
126 mContext.sendBroadcast(intent);
127 }
128 }
129
130 private synchronized final void updateAudioRoute() {
131 if (mAudioRouteNeedsUpdate) {
132 mAudioManager.setWiredHeadsetOn(mHeadsetState == 1);
133 mAudioRouteNeedsUpdate = false;
134 }
135 }
136
137 private final Handler mHandler = new Handler() {
138 @Override
139 public void handleMessage(Message msg) {
140 updateAudioRoute();
141 }
142 };
143
144}