WiredAccessoryManager.java revision 4a5eeb9c727d77bb57fef87a70c8c9cc23dbfee3
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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.os.PowerManager;
27import android.os.PowerManager.WakeLock;
28import android.os.UEventObserver;
29import android.util.Slog;
30import android.media.AudioManager;
31import android.util.Log;
32import android.view.InputDevice;
33
34import com.android.internal.R;
35import com.android.server.input.InputManagerService;
36import com.android.server.input.InputManagerService.WiredAccessoryCallbacks;
37import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT;
38import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT;
39import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT;
40import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT;
41
42import java.io.File;
43import java.io.FileReader;
44import java.io.FileNotFoundException;
45import java.util.ArrayList;
46import java.util.List;
47import java.util.Locale;
48
49/**
50 * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using
51 * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver
52 * subsystem.
53 */
54final class WiredAccessoryManager implements WiredAccessoryCallbacks {
55    private static final String TAG = WiredAccessoryManager.class.getSimpleName();
56    private static final boolean LOG = true;
57
58    private static final int BIT_HEADSET = (1 << 0);
59    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
60    private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
61    private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
62    private static final int BIT_HDMI_AUDIO = (1 << 4);
63    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
64                                                   BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
65                                                   BIT_HDMI_AUDIO);
66
67    private static final String NAME_H2W = "h2w";
68    private static final String NAME_USB_AUDIO = "usb_audio";
69    private static final String NAME_HDMI_AUDIO = "hdmi_audio";
70    private static final String NAME_HDMI = "hdmi";
71
72    private static final int MSG_NEW_DEVICE_STATE = 1;
73    private static final int MSG_SYSTEM_READY = 2;
74
75    private final Object mLock = new Object();
76
77    private final WakeLock mWakeLock;  // held while there is a pending route change
78    private final AudioManager mAudioManager;
79
80    private int mHeadsetState;
81
82    private int mSwitchValues;
83
84    private final WiredAccessoryObserver mObserver;
85    private final InputManagerService mInputManager;
86
87    private final boolean mUseDevInputEventForAudioJack;
88
89    public WiredAccessoryManager(Context context, InputManagerService inputManager) {
90        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
91        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");
92        mWakeLock.setReferenceCounted(false);
93        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
94        mInputManager = inputManager;
95
96        mUseDevInputEventForAudioJack =
97                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
98
99        mObserver = new WiredAccessoryObserver();
100    }
101
102    private void onSystemReady() {
103        if (mUseDevInputEventForAudioJack) {
104            int switchValues = 0;
105            if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) {
106                switchValues |= SW_HEADPHONE_INSERT_BIT;
107            }
108            if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) {
109                switchValues |= SW_MICROPHONE_INSERT_BIT;
110            }
111            notifyWiredAccessoryChanged(0, switchValues,
112                    SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT);
113        }
114
115        mObserver.init();
116    }
117
118    @Override
119    public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
120        if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos
121                + " bits=" + switchCodeToString(switchValues, switchMask)
122                + " mask=" + Integer.toHexString(switchMask));
123
124        synchronized (mLock) {
125            int headset;
126            mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
127            switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) {
128                case 0:
129                    headset = 0;
130                    break;
131
132                case SW_HEADPHONE_INSERT_BIT:
133                    headset = BIT_HEADSET_NO_MIC;
134                    break;
135
136                case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT:
137                    headset = BIT_HEADSET;
138                    break;
139
140                case SW_MICROPHONE_INSERT_BIT:
141                    headset = BIT_HEADSET;
142                    break;
143
144                default:
145                    headset = 0;
146                    break;
147            }
148
149            updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset);
150        }
151    }
152
153    @Override
154    public void systemReady() {
155        synchronized (mLock) {
156            mWakeLock.acquire();
157
158            Message msg = mHandler.obtainMessage(MSG_SYSTEM_READY, 0, 0, null);
159            mHandler.sendMessage(msg);
160        }
161    }
162
163    /**
164     * Compare the existing headset state with the new state and pass along accordingly. Note
165     * that this only supports a single headset at a time. Inserting both a usb and jacked headset
166     * results in support for the last one plugged in. Similarly, unplugging either is seen as
167     * unplugging all.
168     *
169     * @param newName One of the NAME_xxx variables defined above.
170     * @param newState 0 or one of the BIT_xxx variables defined above.
171     */
172    private void updateLocked(String newName, int newState) {
173        // Retain only relevant bits
174        int headsetState = newState & SUPPORTED_HEADSETS;
175        int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
176        int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
177        int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
178        boolean h2wStateChange = true;
179        boolean usbStateChange = true;
180        if (LOG) Slog.v(TAG, "newName=" + newName
181                + " newState=" + newState
182                + " headsetState=" + headsetState
183                + " prev headsetState=" + mHeadsetState);
184
185        if (mHeadsetState == headsetState) {
186            Log.e(TAG, "No state change.");
187            return;
188        }
189
190        // reject all suspect transitions: only accept state changes from:
191        // - a: 0 headset to 1 headset
192        // - b: 1 headset to 0 headset
193        if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) {
194            Log.e(TAG, "Invalid combination, unsetting h2w flag");
195            h2wStateChange = false;
196        }
197        // - c: 0 usb headset to 1 usb headset
198        // - d: 1 usb headset to 0 usb headset
199        if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) {
200            Log.e(TAG, "Invalid combination, unsetting usb flag");
201            usbStateChange = false;
202        }
203        if (!h2wStateChange && !usbStateChange) {
204            Log.e(TAG, "invalid transition, returning ...");
205            return;
206        }
207
208        mWakeLock.acquire();
209
210        Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
211                mHeadsetState, newName);
212        mHandler.sendMessage(msg);
213
214        mHeadsetState = headsetState;
215    }
216
217    private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
218        @Override
219        public void handleMessage(Message msg) {
220            switch (msg.what) {
221                case MSG_NEW_DEVICE_STATE:
222                    setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
223                    mWakeLock.release();
224                    break;
225                case MSG_SYSTEM_READY:
226                    onSystemReady();
227                    mWakeLock.release();
228                    break;
229            }
230        }
231    };
232
233    private void setDevicesState(
234            int headsetState, int prevHeadsetState, String headsetName) {
235        synchronized (mLock) {
236            int allHeadsets = SUPPORTED_HEADSETS;
237            for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
238                if ((curHeadset & allHeadsets) != 0) {
239                    setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
240                    allHeadsets &= ~curHeadset;
241                }
242            }
243        }
244    }
245
246    private void setDeviceStateLocked(int headset,
247            int headsetState, int prevHeadsetState, String headsetName) {
248        if ((headsetState & headset) != (prevHeadsetState & headset)) {
249            int device;
250            int state;
251
252            if ((headsetState & headset) != 0) {
253                state = 1;
254            } else {
255                state = 0;
256            }
257
258            if (headset == BIT_HEADSET) {
259                device = AudioManager.DEVICE_OUT_WIRED_HEADSET;
260            } else if (headset == BIT_HEADSET_NO_MIC){
261                device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
262            } else if (headset == BIT_USB_HEADSET_ANLG) {
263                device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
264            } else if (headset == BIT_USB_HEADSET_DGTL) {
265                device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
266            } else if (headset == BIT_HDMI_AUDIO) {
267                device = AudioManager.DEVICE_OUT_AUX_DIGITAL;
268            } else {
269                Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
270                return;
271            }
272
273            if (LOG)
274                Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected"));
275
276            mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);
277        }
278    }
279
280    private String switchCodeToString(int switchValues, int switchMask) {
281        StringBuffer sb = new StringBuffer();
282        if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 &&
283                (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) {
284            sb.append("SW_HEADPHONE_INSERT ");
285        }
286        if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 &&
287                (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) {
288            sb.append("SW_MICROPHONE_INSERT");
289        }
290        return sb.toString();
291    }
292
293    class WiredAccessoryObserver extends UEventObserver {
294        private final List<UEventInfo> mUEventInfo;
295
296        public WiredAccessoryObserver() {
297            mUEventInfo = makeObservedUEventList();
298        }
299
300        void init() {
301            synchronized (mLock) {
302                if (LOG) Slog.v(TAG, "init()");
303                char[] buffer = new char[1024];
304
305                for (int i = 0; i < mUEventInfo.size(); ++i) {
306                    UEventInfo uei = mUEventInfo.get(i);
307                    try {
308                        int curState;
309                        FileReader file = new FileReader(uei.getSwitchStatePath());
310                        int len = file.read(buffer, 0, 1024);
311                        file.close();
312                        curState = Integer.valueOf((new String(buffer, 0, len)).trim());
313
314                        if (curState > 0) {
315                            updateStateLocked(uei.getDevPath(), uei.getDevName(), curState);
316                        }
317                    } catch (FileNotFoundException e) {
318                        Slog.w(TAG, uei.getSwitchStatePath() +
319                                " not found while attempting to determine initial switch state");
320                    } catch (Exception e) {
321                        Slog.e(TAG, "" , e);
322                    }
323                }
324            }
325
326            // At any given time accessories could be inserted
327            // one on the board, one on the dock and one on HDMI:
328            // observe three UEVENTs
329            for (int i = 0; i < mUEventInfo.size(); ++i) {
330                UEventInfo uei = mUEventInfo.get(i);
331                startObserving("DEVPATH="+uei.getDevPath());
332            }
333        }
334
335        private List<UEventInfo> makeObservedUEventList() {
336            List<UEventInfo> retVal = new ArrayList<UEventInfo>();
337            UEventInfo uei;
338
339            // Monitor h2w
340            if (!mUseDevInputEventForAudioJack) {
341                uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC);
342                if (uei.checkSwitchExists()) {
343                    retVal.add(uei);
344                } else {
345                    Slog.w(TAG, "This kernel does not have wired headset support");
346                }
347            }
348
349            // Monitor USB
350            uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL);
351            if (uei.checkSwitchExists()) {
352                retVal.add(uei);
353            } else {
354                Slog.w(TAG, "This kernel does not have usb audio support");
355            }
356
357            // Monitor HDMI
358            //
359            // If the kernel has support for the "hdmi_audio" switch, use that.  It will be
360            // signalled only when the HDMI driver has a video mode configured, and the downstream
361            // sink indicates support for audio in its EDID.
362            //
363            // If the kernel does not have an "hdmi_audio" switch, just fall back on the older
364            // "hdmi" switch instead.
365            uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0);
366            if (uei.checkSwitchExists()) {
367                retVal.add(uei);
368            } else {
369                uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0);
370                if (uei.checkSwitchExists()) {
371                    retVal.add(uei);
372                } else {
373                    Slog.w(TAG, "This kernel does not have HDMI audio support");
374                }
375            }
376
377            return retVal;
378        }
379
380        @Override
381        public void onUEvent(UEventObserver.UEvent event) {
382            if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
383
384            try {
385                String devPath = event.get("DEVPATH");
386                String name = event.get("SWITCH_NAME");
387                int state = Integer.parseInt(event.get("SWITCH_STATE"));
388                synchronized (mLock) {
389                    updateStateLocked(devPath, name, state);
390                }
391            } catch (NumberFormatException e) {
392                Slog.e(TAG, "Could not parse switch state from event " + event);
393            }
394        }
395
396        private void updateStateLocked(String devPath, String name, int state) {
397            for (int i = 0; i < mUEventInfo.size(); ++i) {
398                UEventInfo uei = mUEventInfo.get(i);
399                if (devPath.equals(uei.getDevPath())) {
400                    updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state));
401                    return;
402                }
403            }
404        }
405
406        private final class UEventInfo {
407            private final String mDevName;
408            private final int mState1Bits;
409            private final int mState2Bits;
410
411            public UEventInfo(String devName, int state1Bits, int state2Bits) {
412                mDevName = devName;
413                mState1Bits = state1Bits;
414                mState2Bits = state2Bits;
415            }
416
417            public String getDevName() { return mDevName; }
418
419            public String getDevPath() {
420                return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName);
421            }
422
423            public String getSwitchStatePath() {
424                return String.format(Locale.US, "/sys/class/switch/%s/state", mDevName);
425            }
426
427            public boolean checkSwitchExists() {
428                File f = new File(getSwitchStatePath());
429                return f.exists();
430            }
431
432            public int computeNewHeadsetState(int headsetState, int switchState) {
433                int preserveMask = ~(mState1Bits | mState2Bits);
434                int setBits = ((switchState == 1) ? mState1Bits :
435                              ((switchState == 2) ? mState2Bits : 0));
436
437                return ((headsetState & preserveMask) | setBits);
438            }
439        }
440    }
441}
442