19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage com.android.server;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.app.ActivityManagerNative;
209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Context;
219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Intent;
229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Handler;
239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Message;
249ac932179c704822c7ae5c7accb02119b578254eNick Pellyimport android.os.PowerManager;
259ac932179c704822c7ae5c7accb02119b578254eNick Pellyimport android.os.PowerManager.WakeLock;
269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.UEventObserver;
279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log;
289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.media.AudioManager;
299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileReader;
319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileNotFoundException;
329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <p>HeadsetObserver monitors for a wired headset.
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectclass HeadsetObserver extends UEventObserver {
379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String TAG = HeadsetObserver.class.getSimpleName();
38e7096ebba1f188871d0c5f04055d21c6153b9907Eric Olsen    private static final boolean LOG = true;
399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
44a553c25b33c99b345cf1c8688f8df0ed8df14e5aEric Laurent    private static final int BIT_HEADSET = (1 << 0);
45a553c25b33c99b345cf1c8688f8df0ed8df14e5aEric Laurent    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
462083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC);
472083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent    private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
48a553c25b33c99b345cf1c8688f8df0ed8df14e5aEric Laurent
499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private int mHeadsetState;
50a553c25b33c99b345cf1c8688f8df0ed8df14e5aEric Laurent    private int mPrevHeadsetState;
519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private String mHeadsetName;
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
539ac932179c704822c7ae5c7accb02119b578254eNick Pelly    private final Context mContext;
549ac932179c704822c7ae5c7accb02119b578254eNick Pelly    private final WakeLock mWakeLock;  // held while there is a pending route change
559ac932179c704822c7ae5c7accb02119b578254eNick Pelly
569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public HeadsetObserver(Context context) {
579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        mContext = context;
589ac932179c704822c7ae5c7accb02119b578254eNick Pelly        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
599ac932179c704822c7ae5c7accb02119b578254eNick Pelly        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
609ac932179c704822c7ae5c7accb02119b578254eNick Pelly        mWakeLock.setReferenceCounted(false);
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        startObserving(HEADSET_UEVENT_MATCH);
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        init();  // set initial status
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    @Override
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void onUEvent(UEventObserver.UEvent event) {
699a5e3e115fc69181e143bdb9ec455e0f94fbc11fJoe Onorato        if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString());
709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (NumberFormatException e) {
749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Log.e(TAG, "Could not parse switch state from event " + event);
759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private synchronized final void init() {
799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        char[] buffer = new char[1024];
809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String newName = mHeadsetName;
829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        int newState = mHeadsetState;
83a553c25b33c99b345cf1c8688f8df0ed8df14e5aEric Laurent        mPrevHeadsetState = mHeadsetState;
849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            FileReader file = new FileReader(HEADSET_STATE_PATH);
869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            int len = file.read(buffer, 0, 1024);
879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            newState = Integer.valueOf((new String(buffer, 0, len)).trim());
889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            file = new FileReader(HEADSET_NAME_PATH);
909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            len = file.read(buffer, 0, 1024);
919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            newName = new String(buffer, 0, len).trim();
929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (FileNotFoundException e) {
949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Log.w(TAG, "This kernel does not have wired headset support");
959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (Exception e) {
969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Log.e(TAG, "" , e);
979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        update(newName, newState);
1009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private synchronized final void update(String newName, int newState) {
103923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent        // Retain only relevant bits
1042083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        int headsetState = newState & SUPPORTED_HEADSETS;
1052083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        int newOrOld = headsetState | mHeadsetState;
106700aab67eb286b44663f885325f8db6b049cb638Eric Laurent        int delay = 0;
1072083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        // reject all suspect transitions: only accept state changes from:
1082083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        // - a: 0 heaset to 1 headset
1092083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        // - b: 1 headset to 0 headset
1102083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) {
1112083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            return;
1122083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        }
113923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent
1142083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        mHeadsetName = newName;
1152083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        mPrevHeadsetState = mHeadsetState;
1162083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        mHeadsetState = headsetState;
1172083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent
1182083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        if (headsetState == 0) {
1192083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
1202083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            mContext.sendBroadcast(intent);
1212083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // It can take hundreds of ms flush the audio pipeline after
1222083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // apps pause audio playback, but audio route changes are
1232083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // immediate, so delay the route change by 1000ms.
1242083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // This could be improved once the audio sub-system provides an
1252083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // interface to clear the audio pipeline.
126700aab67eb286b44663f885325f8db6b049cb638Eric Laurent            delay = 1000;
1272083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        } else {
128700aab67eb286b44663f885325f8db6b049cb638Eric Laurent            // Insert the same delay for headset connection so that the connection event is not
129700aab67eb286b44663f885325f8db6b049cb638Eric Laurent            // broadcast before the disconnection event in case of fast removal/insertion
130700aab67eb286b44663f885325f8db6b049cb638Eric Laurent            if (mHandler.hasMessages(0)) {
131700aab67eb286b44663f885325f8db6b049cb638Eric Laurent                delay = 1000;
132700aab67eb286b44663f885325f8db6b049cb638Eric Laurent            }
1332083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        }
134700aab67eb286b44663f885325f8db6b049cb638Eric Laurent        mWakeLock.acquire();
135700aab67eb286b44663f885325f8db6b049cb638Eric Laurent        mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
136700aab67eb286b44663f885325f8db6b049cb638Eric Laurent                                                           mHeadsetState,
137700aab67eb286b44663f885325f8db6b049cb638Eric Laurent                                                           mPrevHeadsetState,
138700aab67eb286b44663f885325f8db6b049cb638Eric Laurent                                                           mHeadsetName),
139700aab67eb286b44663f885325f8db6b049cb638Eric Laurent                                    delay);
1402083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent    }
1412083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent
142da4cc34308d65730c404b669926a92e37b378555Eric Laurent    private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
1432083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        int allHeadsets = SUPPORTED_HEADSETS;
1442083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
1452083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            if ((curHeadset & allHeadsets) != 0) {
146da4cc34308d65730c404b669926a92e37b378555Eric Laurent                sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
1472083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent                allHeadsets &= ~curHeadset;
1489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
152da4cc34308d65730c404b669926a92e37b378555Eric Laurent    private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
153da4cc34308d65730c404b669926a92e37b378555Eric Laurent        if ((headsetState & headset) != (prevHeadsetState & headset)) {
1542083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            //  Pack up the values and broadcast them to everyone
1552083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
1562083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
1572083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            int state = 0;
1582083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            int microphone = 0;
159923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent
1602083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            if ((headset & HEADSETS_WITH_MIC) != 0) {
1612083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent                microphone = 1;
162923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent            }
163da4cc34308d65730c404b669926a92e37b378555Eric Laurent            if ((headsetState & headset) != 0) {
164923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent                state = 1;
165923d7d721d37f6ba5148e7d79d61a4fa48e79df2Eric Laurent            }
1662083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            intent.putExtra("state", state);
167da4cc34308d65730c404b669926a92e37b378555Eric Laurent            intent.putExtra("name", headsetName);
1682083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            intent.putExtra("microphone", microphone);
1699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
170da4cc34308d65730c404b669926a92e37b378555Eric Laurent            if (LOG) Log.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
1712083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            // TODO: Should we require a permission?
1722083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent            ActivityManagerNative.broadcastStickyIntent(intent, null);
1732083b297e401957dfae07ca11c29e8c95f440a97Eric Laurent        }
1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private final Handler mHandler = new Handler() {
1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        @Override
1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public void handleMessage(Message msg) {
179da4cc34308d65730c404b669926a92e37b378555Eric Laurent            sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
1809ac932179c704822c7ae5c7accb02119b578254eNick Pelly            mWakeLock.release();
1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1829ac932179c704822c7ae5c7accb02119b578254eNick Pelly    };
1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
184