Home | History | Annotate | Download | only in server
      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 
     17 package com.android.server;
     18 
     19 import android.app.ActivityManagerNative;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.os.Handler;
     23 import android.os.Message;
     24 import android.os.PowerManager;
     25 import android.os.PowerManager.WakeLock;
     26 import android.os.UEventObserver;
     27 import android.util.Slog;
     28 import android.media.AudioManager;
     29 
     30 import java.io.FileReader;
     31 import java.io.FileNotFoundException;
     32 
     33 /**
     34  * <p>HeadsetObserver monitors for a wired headset.
     35  */
     36 class 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