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.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.os.Handler;
     25 import android.os.Message;
     26 import android.os.PowerManager;
     27 import android.os.PowerManager.WakeLock;
     28 import android.os.UEventObserver;
     29 import android.util.Slog;
     30 import android.media.AudioManager;
     31 import android.util.Log;
     32 
     33 import java.io.FileReader;
     34 import java.io.FileNotFoundException;
     35 
     36 /**
     37  * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
     38  */
     39 class WiredAccessoryObserver extends UEventObserver {
     40     private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
     41     private static final boolean LOG = true;
     42     private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */
     43     private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
     44                                                     "/sys/class/switch/h2w/state",
     45                                                     "/sys/class/switch/h2w/name"},
     46                                                    {"DEVPATH=/devices/virtual/switch/usb_audio",
     47                                                     "/sys/class/switch/usb_audio/state",
     48                                                     "/sys/class/switch/usb_audio/name"},
     49                                                    {"DEVPATH=/devices/virtual/switch/hdmi",
     50                                                     "/sys/class/switch/hdmi/state",
     51                                                     "/sys/class/switch/hdmi/name"} };
     52 
     53     private static final int BIT_HEADSET = (1 << 0);
     54     private static final int BIT_HEADSET_NO_MIC = (1 << 1);
     55     private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
     56     private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
     57     private static final int BIT_HDMI_AUDIO = (1 << 4);
     58     private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
     59                                                    BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
     60                                                    BIT_HDMI_AUDIO);
     61     private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
     62 
     63     private int mHeadsetState;
     64     private int mPrevHeadsetState;
     65     private String mHeadsetName;
     66     private int switchState;
     67 
     68     private final Context mContext;
     69     private final WakeLock mWakeLock;  // held while there is a pending route change
     70 
     71     public WiredAccessoryObserver(Context context) {
     72         mContext = context;
     73         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
     74         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver");
     75         mWakeLock.setReferenceCounted(false);
     76 
     77         context.registerReceiver(new BootCompletedReceiver(),
     78             new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
     79     }
     80 
     81     private final class BootCompletedReceiver extends BroadcastReceiver {
     82       @Override
     83       public void onReceive(Context context, Intent intent) {
     84         // At any given time accessories could be inserted
     85         // one on the board, one on the dock and one on HDMI:
     86         // observe three UEVENTs
     87         init();  // set initial status
     88         for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
     89             startObserving(uEventInfo[i][0]);
     90         }
     91       }
     92   }
     93 
     94     @Override
     95     public void onUEvent(UEventObserver.UEvent event) {
     96         if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
     97 
     98         try {
     99             String name = event.get("SWITCH_NAME");
    100             int state = Integer.parseInt(event.get("SWITCH_STATE"));
    101             updateState(name, state);
    102         } catch (NumberFormatException e) {
    103             Slog.e(TAG, "Could not parse switch state from event " + event);
    104         }
    105     }
    106 
    107     private synchronized final void updateState(String name, int state)
    108     {
    109         if (name.equals("usb_audio")) {
    110             switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) |
    111                            ((state == 1) ? BIT_USB_HEADSET_ANLG :
    112                                          ((state == 2) ? BIT_USB_HEADSET_DGTL : 0)));
    113         } else if (name.equals("hdmi")) {
    114             switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
    115                                              BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) |
    116                            ((state == 1) ? BIT_HDMI_AUDIO : 0));
    117         } else {
    118             switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG|
    119                                              BIT_USB_HEADSET_DGTL)) |
    120                             ((state == 1) ? BIT_HEADSET :
    121                                           ((state == 2) ? BIT_HEADSET_NO_MIC : 0)));
    122         }
    123         update(name, switchState);
    124     }
    125 
    126     private synchronized final void init() {
    127         char[] buffer = new char[1024];
    128 
    129         String newName = mHeadsetName;
    130         int newState = mHeadsetState;
    131         mPrevHeadsetState = mHeadsetState;
    132 
    133         if (LOG) Slog.v(TAG, "init()");
    134 
    135         for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
    136             try {
    137                 FileReader file = new FileReader(uEventInfo[i][1]);
    138                 int len = file.read(buffer, 0, 1024);
    139                 file.close();
    140                 newState = Integer.valueOf((new String(buffer, 0, len)).trim());
    141 
    142                 file = new FileReader(uEventInfo[i][2]);
    143                 len = file.read(buffer, 0, 1024);
    144                 file.close();
    145                 newName = new String(buffer, 0, len).trim();
    146 
    147                 if (newState > 0) {
    148                     updateState(newName, newState);
    149                 }
    150 
    151             } catch (FileNotFoundException e) {
    152                 Slog.w(TAG, "This kernel does not have wired headset support");
    153             } catch (Exception e) {
    154                 Slog.e(TAG, "" , e);
    155             }
    156         }
    157     }
    158 
    159     private synchronized final void update(String newName, int newState) {
    160         // Retain only relevant bits
    161         int headsetState = newState & SUPPORTED_HEADSETS;
    162         int newOrOld = headsetState | mHeadsetState;
    163         int delay = 0;
    164         int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
    165         int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
    166         int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
    167         boolean h2wStateChange = true;
    168         boolean usbStateChange = true;
    169         // reject all suspect transitions: only accept state changes from:
    170         // - a: 0 heaset to 1 headset
    171         // - b: 1 headset to 0 headset
    172         if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+","
    173             + "mHeadsetState = "+mHeadsetState);
    174         if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) {
    175             Log.e(TAG, "unsetting h2w flag");
    176             h2wStateChange = false;
    177         }
    178         // - c: 0 usb headset to 1 usb headset
    179         // - d: 1 usb headset to 0 usb headset
    180         if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) {
    181             Log.e(TAG, "unsetting usb flag");
    182             usbStateChange = false;
    183         }
    184         if (!h2wStateChange && !usbStateChange) {
    185             Log.e(TAG, "invalid transition, returning ...");
    186             return;
    187         }
    188 
    189         mHeadsetName = newName;
    190         mPrevHeadsetState = mHeadsetState;
    191         mHeadsetState = headsetState;
    192 
    193         if (headsetState == 0) {
    194             Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    195             mContext.sendBroadcast(intent);
    196             // It can take hundreds of ms flush the audio pipeline after
    197             // apps pause audio playback, but audio route changes are
    198             // immediate, so delay the route change by 1000ms.
    199             // This could be improved once the audio sub-system provides an
    200             // interface to clear the audio pipeline.
    201             delay = 1000;
    202         } else {
    203             // Insert the same delay for headset connection so that the connection event is not
    204             // broadcast before the disconnection event in case of fast removal/insertion
    205             if (mHandler.hasMessages(0)) {
    206                 delay = 1000;
    207             }
    208         }
    209         mWakeLock.acquire();
    210         mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
    211                                                            mHeadsetState,
    212                                                            mPrevHeadsetState,
    213                                                            mHeadsetName),
    214                                     delay);
    215     }
    216 
    217     private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
    218         int allHeadsets = SUPPORTED_HEADSETS;
    219         for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
    220             if ((curHeadset & allHeadsets) != 0) {
    221                 sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
    222                 allHeadsets &= ~curHeadset;
    223             }
    224         }
    225     }
    226 
    227     private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
    228         if ((headsetState & headset) != (prevHeadsetState & headset)) {
    229 
    230             int state = 0;
    231             if ((headsetState & headset) != 0) {
    232                 state = 1;
    233             }
    234             if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) ||
    235                (headset == BIT_HDMI_AUDIO)) {
    236                 Intent intent;
    237 
    238                 //  Pack up the values and broadcast them to everyone
    239                 if (headset == BIT_USB_HEADSET_ANLG) {
    240                     intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
    241                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    242                     intent.putExtra("state", state);
    243                     intent.putExtra("name", headsetName);
    244                     ActivityManagerNative.broadcastStickyIntent(intent, null);
    245                 } else if (headset == BIT_USB_HEADSET_DGTL) {
    246                     intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG);
    247                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    248                     intent.putExtra("state", state);
    249                     intent.putExtra("name", headsetName);
    250                     ActivityManagerNative.broadcastStickyIntent(intent, null);
    251                 } else if (headset == BIT_HDMI_AUDIO) {
    252                     intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG);
    253                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    254                     intent.putExtra("state", state);
    255                     intent.putExtra("name", headsetName);
    256                     ActivityManagerNative.broadcastStickyIntent(intent, null);
    257                 }
    258 
    259                 if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName);
    260                 // TODO: Should we require a permission?
    261             }
    262             if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) {
    263 
    264                 //  Pack up the values and broadcast them to everyone
    265                 Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
    266                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    267                 //int state = 0;
    268                 int microphone = 0;
    269 
    270                 if ((headset & HEADSETS_WITH_MIC) != 0) {
    271                     microphone = 1;
    272                 }
    273 
    274                 intent.putExtra("state", state);
    275                 intent.putExtra("name", headsetName);
    276                 intent.putExtra("microphone", microphone);
    277 
    278                 if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
    279                 // TODO: Should we require a permission?
    280                 ActivityManagerNative.broadcastStickyIntent(intent, null);
    281             }
    282         }
    283     }
    284 
    285     private final Handler mHandler = new Handler() {
    286         @Override
    287         public void handleMessage(Message msg) {
    288             sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
    289             mWakeLock.release();
    290         }
    291     };
    292 }
    293