1/*
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.PowerManager;
25import android.os.PowerManager.WakeLock;
26import android.os.UEventObserver;
27import android.util.Slog;
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    private static final boolean LOG = true;
39
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
44    private static final int BIT_HEADSET = (1 << 0);
45    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
46    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC);
47    private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
48
49    private int mHeadsetState;
50    private int mPrevHeadsetState;
51    private String mHeadsetName;
52
53    private final Context mContext;
54    private final WakeLock mWakeLock;  // held while there is a pending route change
55
56    public HeadsetObserver(Context context) {
57        mContext = context;
58        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
59        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
60        mWakeLock.setReferenceCounted(false);
61
62        startObserving(HEADSET_UEVENT_MATCH);
63
64        init();  // set initial status
65    }
66
67    @Override
68    public void onUEvent(UEventObserver.UEvent event) {
69        if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
70
71        try {
72            update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
73        } catch (NumberFormatException e) {
74            Slog.e(TAG, "Could not parse switch state from event " + event);
75        }
76    }
77
78    private synchronized final void init() {
79        char[] buffer = new char[1024];
80
81        String newName = mHeadsetName;
82        int newState = mHeadsetState;
83        mPrevHeadsetState = mHeadsetState;
84        try {
85            FileReader file = new FileReader(HEADSET_STATE_PATH);
86            int len = file.read(buffer, 0, 1024);
87            newState = Integer.valueOf((new String(buffer, 0, len)).trim());
88
89            file = new FileReader(HEADSET_NAME_PATH);
90            len = file.read(buffer, 0, 1024);
91            newName = new String(buffer, 0, len).trim();
92
93        } catch (FileNotFoundException e) {
94            Slog.w(TAG, "This kernel does not have wired headset support");
95        } catch (Exception e) {
96            Slog.e(TAG, "" , e);
97        }
98
99        update(newName, newState);
100    }
101
102    private synchronized final void update(String newName, int newState) {
103        // Retain only relevant bits
104        int headsetState = newState & SUPPORTED_HEADSETS;
105        int newOrOld = headsetState | mHeadsetState;
106        int delay = 0;
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        }
113
114        mHeadsetName = newName;
115        mPrevHeadsetState = mHeadsetState;
116        mHeadsetState = headsetState;
117
118        if (headsetState == 0) {
119            Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
120            mContext.sendBroadcast(intent);
121            // It can take hundreds of ms flush the audio pipeline after
122            // apps pause audio playback, but audio route changes are
123            // immediate, so delay the route change by 1000ms.
124            // This could be improved once the audio sub-system provides an
125            // interface to clear the audio pipeline.
126            delay = 1000;
127        } else {
128            // Insert the same delay for headset connection so that the connection event is not
129            // broadcast before the disconnection event in case of fast removal/insertion
130            if (mHandler.hasMessages(0)) {
131                delay = 1000;
132            }
133        }
134        mWakeLock.acquire();
135        mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
136                                                           mHeadsetState,
137                                                           mPrevHeadsetState,
138                                                           mHeadsetName),
139                                    delay);
140    }
141
142    private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
143        int allHeadsets = SUPPORTED_HEADSETS;
144        for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
145            if ((curHeadset & allHeadsets) != 0) {
146                sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
147                allHeadsets &= ~curHeadset;
148            }
149        }
150    }
151
152    private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
153        if ((headsetState & headset) != (prevHeadsetState & headset)) {
154            //  Pack up the values and broadcast them to everyone
155            Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
156            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
157            int state = 0;
158            int microphone = 0;
159
160            if ((headset & HEADSETS_WITH_MIC) != 0) {
161                microphone = 1;
162            }
163            if ((headsetState & headset) != 0) {
164                state = 1;
165            }
166            intent.putExtra("state", state);
167            intent.putExtra("name", headsetName);
168            intent.putExtra("microphone", microphone);
169
170            if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
171            // TODO: Should we require a permission?
172            ActivityManagerNative.broadcastStickyIntent(intent, null);
173        }
174    }
175
176    private final Handler mHandler = new Handler() {
177        @Override
178        public void handleMessage(Message msg) {
179            sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
180            mWakeLock.release();
181        }
182    };
183}
184