Home | History | Annotate | Download | only in media
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.media;
      6 
      7 import android.bluetooth.BluetoothAdapter;
      8 import android.bluetooth.BluetoothManager;
      9 import android.content.BroadcastReceiver;
     10 import android.content.ContentResolver;
     11 import android.content.Context;
     12 import android.content.Intent;
     13 import android.content.IntentFilter;
     14 import android.content.pm.PackageManager;
     15 import android.database.ContentObserver;
     16 import android.media.AudioFormat;
     17 import android.media.AudioManager;
     18 import android.media.AudioRecord;
     19 import android.media.AudioTrack;
     20 import android.media.audiofx.AcousticEchoCanceler;
     21 import android.os.Build;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Process;
     25 import android.provider.Settings;
     26 import android.util.Log;
     27 
     28 import org.chromium.base.CalledByNative;
     29 import org.chromium.base.JNINamespace;
     30 
     31 import java.util.ArrayList;
     32 import java.util.Arrays;
     33 import java.util.List;
     34 
     35 @JNINamespace("media")
     36 class AudioManagerAndroid {
     37     private static final String TAG = "AudioManagerAndroid";
     38 
     39     // Set to true to enable debug logs. Always check in as false.
     40     private static final boolean DEBUG = false;
     41 
     42     /** Simple container for device information. */
     43     private static class AudioDeviceName {
     44         private final int mId;
     45         private final String mName;
     46 
     47         private AudioDeviceName(int id, String name) {
     48             mId = id;
     49             mName = name;
     50         }
     51 
     52         @CalledByNative("AudioDeviceName")
     53         private String id() { return String.valueOf(mId); }
     54 
     55         @CalledByNative("AudioDeviceName")
     56         private String name() { return mName; }
     57     }
     58 
     59     // Supported audio device types.
     60     private static final int DEVICE_INVALID = -1;
     61     private static final int DEVICE_SPEAKERPHONE = 0;
     62     private static final int DEVICE_WIRED_HEADSET = 1;
     63     private static final int DEVICE_EARPIECE = 2;
     64     private static final int DEVICE_BLUETOOTH_HEADSET = 3;
     65     private static final int DEVICE_COUNT = 4;
     66 
     67     // Maps audio device types to string values. This map must be in sync
     68     // with the device types above.
     69     // TODO(henrika): add support for proper detection of device names and
     70     // localize the name strings by using resource strings.
     71     private static final String[] DEVICE_NAMES = new String[] {
     72         "Speakerphone",
     73         "Wired headset",    // With or without microphone
     74         "Headset earpiece", // Only available on mobile phones
     75         "Bluetooth headset",
     76     };
     77 
     78     // List of valid device types.
     79     private static final Integer[] VALID_DEVICES = new Integer[] {
     80         DEVICE_SPEAKERPHONE,
     81         DEVICE_WIRED_HEADSET,
     82         DEVICE_EARPIECE,
     83         DEVICE_BLUETOOTH_HEADSET,
     84     };
     85 
     86     // The device does not have any audio device.
     87     static final int STATE_NO_DEVICE_SELECTED = 0;
     88     // The speakerphone is on and an associated microphone is used.
     89     static final int STATE_SPEAKERPHONE_ON = 1;
     90     // The phone's earpiece is on and an associated microphone is used.
     91     static final int STATE_EARPIECE_ON = 2;
     92     // A wired headset (with or without a microphone) is plugged in.
     93     static final int STATE_WIRED_HEADSET_ON = 3;
     94     // The audio stream is being directed to a Bluetooth headset.
     95     static final int STATE_BLUETOOTH_ON = 4;
     96     // We've requested that the audio stream be directed to Bluetooth, but
     97     // have not yet received a response from the framework.
     98     static final int STATE_BLUETOOTH_TURNING_ON = 5;
     99     // We've requested that the audio stream stop being directed to
    100     // Bluetooth, but have not yet received a response from the framework.
    101     static final int STATE_BLUETOOTH_TURNING_OFF = 6;
    102     // TODO(henrika): document the valid state transitions.
    103 
    104     // Use 44.1kHz as the default sampling rate.
    105     private static final int DEFAULT_SAMPLING_RATE = 44100;
    106     // Randomly picked up frame size which is close to return value on N4.
    107     // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
    108     // fails.
    109     private static final int DEFAULT_FRAME_PER_BUFFER = 256;
    110 
    111     private final AudioManager mAudioManager;
    112     private final Context mContext;
    113     private final long mNativeAudioManagerAndroid;
    114 
    115     private boolean mHasBluetoothPermission = false;
    116     private boolean mIsInitialized = false;
    117     private boolean mSavedIsSpeakerphoneOn;
    118     private boolean mSavedIsMicrophoneMute;
    119 
    120     private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED;
    121 
    122     // Lock to protect |mAudioDevices| which can be accessed from the main
    123     // thread and the audio manager thread.
    124     private final Object mLock = new Object();
    125 
    126     // Contains a list of currently available audio devices.
    127     private boolean[] mAudioDevices = new boolean[DEVICE_COUNT];
    128 
    129     private final ContentResolver mContentResolver;
    130     private SettingsObserver mSettingsObserver = null;
    131     private SettingsObserverThread mSettingsObserverThread = null;
    132     private int mCurrentVolume;
    133     private final Object mSettingsObserverLock = new Object();
    134 
    135     // Broadcast receiver for wired headset intent broadcasts.
    136     private BroadcastReceiver mWiredHeadsetReceiver;
    137 
    138     /** Construction */
    139     @CalledByNative
    140     private static AudioManagerAndroid createAudioManagerAndroid(
    141             Context context,
    142             long nativeAudioManagerAndroid) {
    143         return new AudioManagerAndroid(context, nativeAudioManagerAndroid);
    144     }
    145 
    146     private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) {
    147         mContext = context;
    148         mNativeAudioManagerAndroid = nativeAudioManagerAndroid;
    149         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    150         mContentResolver = mContext.getContentResolver();
    151     }
    152 
    153     /**
    154      * Saves the initial speakerphone and microphone state.
    155      * Populates the list of available audio devices and registers receivers
    156      * for broadcasted intents related to wired headset and bluetooth devices.
    157      */
    158     @CalledByNative
    159     public void init() {
    160         if (mIsInitialized)
    161             return;
    162 
    163         synchronized (mLock) {
    164             for (int i = 0; i < DEVICE_COUNT; ++i) {
    165                 mAudioDevices[i] = false;
    166             }
    167         }
    168 
    169         // Store microphone mute state and speakerphone state so it can
    170         // be restored when closing.
    171         mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn();
    172         mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute();
    173 
    174         // Initialize audio device list with things we know is always available.
    175         synchronized (mLock) {
    176             if (hasEarpiece()) {
    177                 mAudioDevices[DEVICE_EARPIECE] = true;
    178             }
    179             mAudioDevices[DEVICE_SPEAKERPHONE] = true;
    180         }
    181 
    182         // Register receiver for broadcasted intents related to adding/
    183         // removing a wired headset (Intent.ACTION_HEADSET_PLUG).
    184         // Also starts routing to the wired headset/headphone if one is
    185         // already attached (can be overridden by a Bluetooth headset).
    186         registerForWiredHeadsetIntentBroadcast();
    187 
    188         // Start routing to Bluetooth if there's a connected device.
    189         // TODO(henrika): the actual routing part is not implemented yet.
    190         // All we do currently is to detect if BT headset is attached or not.
    191         initBluetooth();
    192 
    193         mIsInitialized = true;
    194 
    195         mSettingsObserverThread = new SettingsObserverThread();
    196         synchronized (mSettingsObserverLock) {
    197             try {
    198                 mSettingsObserverThread.start();
    199                 mSettingsObserverLock.wait();
    200             } catch (InterruptedException e) {
    201                 Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage());
    202             }
    203         }
    204     }
    205 
    206     /**
    207      * Unregister all previously registered intent receivers and restore
    208      * the stored state (stored in {@link #init()}).
    209      */
    210     @CalledByNative
    211     public void close() {
    212         if (!mIsInitialized)
    213             return;
    214 
    215         if (mSettingsObserverThread != null) {
    216             mSettingsObserverThread = null;
    217         }
    218         if (mSettingsObserver != null) {
    219             mContentResolver.unregisterContentObserver(mSettingsObserver);
    220             mSettingsObserver = null;
    221         }
    222 
    223         unregisterForWiredHeadsetIntentBroadcast();
    224 
    225         // Restore previously stored audio states.
    226         setMicrophoneMute(mSavedIsMicrophoneMute);
    227         setSpeakerphoneOn(mSavedIsSpeakerphoneOn);
    228 
    229         mIsInitialized = false;
    230     }
    231 
    232     @CalledByNative
    233     public void setMode(int mode) {
    234         try {
    235             mAudioManager.setMode(mode);
    236         } catch (SecurityException e) {
    237             Log.e(TAG, "setMode exception: " + e.getMessage());
    238             logDeviceInfo();
    239         }
    240     }
    241 
    242     /**
    243      * Activates, i.e., starts routing audio to, the specified audio device.
    244      *
    245      * @param deviceId Unique device ID (integer converted to string)
    246      * representing the selected device. This string is empty if the so-called
    247      * default device is selected.
    248      */
    249     @CalledByNative
    250     public void setDevice(String deviceId) {
    251         boolean devices[] = null;
    252         synchronized (mLock) {
    253             devices = mAudioDevices.clone();
    254         }
    255         if (deviceId.isEmpty()) {
    256             logd("setDevice: default");
    257             // Use a special selection scheme if the default device is selected.
    258             // The "most unique" device will be selected; Bluetooth first, then
    259             // wired headset and last the speaker phone.
    260             if (devices[DEVICE_BLUETOOTH_HEADSET]) {
    261                 // TODO(henrika): possibly need improvements here if we are
    262                 // in a STATE_BLUETOOTH_TURNING_OFF state.
    263                 setAudioDevice(DEVICE_BLUETOOTH_HEADSET);
    264             } else if (devices[DEVICE_WIRED_HEADSET]) {
    265                 setAudioDevice(DEVICE_WIRED_HEADSET);
    266             } else {
    267                 setAudioDevice(DEVICE_SPEAKERPHONE);
    268             }
    269         } else {
    270             logd("setDevice: " + deviceId);
    271             // A non-default device is specified. Verify that it is valid
    272             // device, and if so, start using it.
    273             List<Integer> validIds = Arrays.asList(VALID_DEVICES);
    274             Integer id = Integer.valueOf(deviceId);
    275             if (validIds.contains(id)) {
    276                 setAudioDevice(id.intValue());
    277             } else {
    278                 loge("Invalid device ID!");
    279             }
    280         }
    281     }
    282 
    283     /**
    284      * @return the current list of available audio devices.
    285      * Note that this call does not trigger any update of the list of devices,
    286      * it only copies the current state in to the output array.
    287      */
    288     @CalledByNative
    289     public AudioDeviceName[] getAudioInputDeviceNames() {
    290         synchronized (mLock) {
    291             List<String> devices = new ArrayList<String>();
    292             AudioDeviceName[] array = new AudioDeviceName[getNumOfAudioDevicesWithLock()];
    293             int i = 0;
    294             for (int id = 0; id < DEVICE_COUNT; ++id) {
    295                 if (mAudioDevices[id]) {
    296                     array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]);
    297                     devices.add(DEVICE_NAMES[id]);
    298                     i++;
    299                 }
    300             }
    301             logd("getAudioInputDeviceNames: " + devices);
    302             return array;
    303         }
    304     }
    305 
    306     @CalledByNative
    307     private int getNativeOutputSampleRate() {
    308         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    309             String sampleRateString = mAudioManager.getProperty(
    310                     AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
    311             return (sampleRateString == null ?
    312                     DEFAULT_SAMPLING_RATE : Integer.parseInt(sampleRateString));
    313         } else {
    314             return DEFAULT_SAMPLING_RATE;
    315         }
    316     }
    317 
    318   /**
    319    * Returns the minimum frame size required for audio input.
    320    *
    321    * @param sampleRate sampling rate
    322    * @param channels number of channels
    323    */
    324     @CalledByNative
    325     private static int getMinInputFrameSize(int sampleRate, int channels) {
    326         int channelConfig;
    327         if (channels == 1) {
    328             channelConfig = AudioFormat.CHANNEL_IN_MONO;
    329         } else if (channels == 2) {
    330             channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    331         } else {
    332             return -1;
    333         }
    334         return AudioRecord.getMinBufferSize(
    335                 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
    336     }
    337 
    338   /**
    339    * Returns the minimum frame size required for audio output.
    340    *
    341    * @param sampleRate sampling rate
    342    * @param channels number of channels
    343    */
    344     @CalledByNative
    345     private static int getMinOutputFrameSize(int sampleRate, int channels) {
    346         int channelConfig;
    347         if (channels == 1) {
    348             channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    349         } else if (channels == 2) {
    350             channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
    351         } else {
    352             return -1;
    353         }
    354         return AudioTrack.getMinBufferSize(
    355                 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
    356     }
    357 
    358     @CalledByNative
    359     private boolean isAudioLowLatencySupported() {
    360         return mContext.getPackageManager().hasSystemFeature(
    361                 PackageManager.FEATURE_AUDIO_LOW_LATENCY);
    362     }
    363 
    364     @CalledByNative
    365     private int getAudioLowLatencyOutputFrameSize() {
    366         String framesPerBuffer =
    367                 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    368         return (framesPerBuffer == null ?
    369                 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer));
    370     }
    371 
    372     @CalledByNative
    373     public static boolean shouldUseAcousticEchoCanceler() {
    374         // AcousticEchoCanceler was added in API level 16 (Jelly Bean).
    375         // Next is a list of device models which have been vetted for good
    376         // quality platform echo cancellation.
    377         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
    378                AcousticEchoCanceler.isAvailable() &&
    379                (Build.MODEL.equals("Nexus 5") ||
    380                 Build.MODEL.equals("Nexus 7"));
    381     }
    382 
    383     /** Sets the speaker phone mode. */
    384     public void setSpeakerphoneOn(boolean on) {
    385         boolean wasOn = mAudioManager.isSpeakerphoneOn();
    386         if (wasOn == on) {
    387             return;
    388         }
    389         mAudioManager.setSpeakerphoneOn(on);
    390     }
    391 
    392     /** Sets the microphone mute state. */
    393     public void setMicrophoneMute(boolean on) {
    394         boolean wasMuted = mAudioManager.isMicrophoneMute();
    395         if (wasMuted == on) {
    396             return;
    397         }
    398         mAudioManager.setMicrophoneMute(on);
    399     }
    400 
    401     /** Gets  the current microphone mute state. */
    402     public boolean isMicrophoneMute() {
    403         return mAudioManager.isMicrophoneMute();
    404     }
    405 
    406     /** Gets the current earpice state. */
    407     private boolean hasEarpiece() {
    408         return mContext.getPackageManager().hasSystemFeature(
    409             PackageManager.FEATURE_TELEPHONY);
    410     }
    411 
    412     /**
    413      * Registers receiver for the broadcasted intent when a wired headset is
    414      * plugged in or unplugged. The received intent will have an extra
    415      * 'state' value where 0 means unplugged, and 1 means plugged.
    416      */
    417     private void registerForWiredHeadsetIntentBroadcast() {
    418         IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
    419 
    420         /**
    421          * Receiver which handles changes in wired headset availablilty.
    422          */
    423         mWiredHeadsetReceiver = new BroadcastReceiver() {
    424             private static final int STATE_UNPLUGGED = 0;
    425             private static final int STATE_PLUGGED = 1;
    426             private static final int HAS_NO_MIC = 0;
    427             private static final int HAS_MIC = 1;
    428 
    429             @Override
    430             public void onReceive(Context context, Intent intent) {
    431                 String action = intent.getAction();
    432                 if (!action.equals(Intent.ACTION_HEADSET_PLUG)) {
    433                     return;
    434                 }
    435                 int state = intent.getIntExtra("state", STATE_UNPLUGGED);
    436                 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
    437                 String name = intent.getStringExtra("name");
    438                 logd("==> onReceive: s=" + state
    439                         + ", m=" + microphone
    440                         + ", n=" + name
    441                         + ", sb=" + isInitialStickyBroadcast());
    442 
    443                 switch (state) {
    444                     case STATE_UNPLUGGED:
    445                         synchronized (mLock) {
    446                             // Wired headset and earpiece are mutually exclusive.
    447                             mAudioDevices[DEVICE_WIRED_HEADSET] = false;
    448                             if (hasEarpiece()) {
    449                                 mAudioDevices[DEVICE_EARPIECE] = true;
    450                             }
    451                         }
    452                         // If wired headset was used before it was unplugged,
    453                         // switch to speaker phone. If it was not in use; just
    454                         // log the change.
    455                         if (mAudioDeviceState == STATE_WIRED_HEADSET_ON) {
    456                             setAudioDevice(DEVICE_SPEAKERPHONE);
    457                         } else {
    458                             reportUpdate();
    459                         }
    460                         break;
    461                     case STATE_PLUGGED:
    462                         synchronized (mLock) {
    463                             // Wired headset and earpiece are mutually exclusive.
    464                             mAudioDevices[DEVICE_WIRED_HEADSET] = true;
    465                             mAudioDevices[DEVICE_EARPIECE] = false;
    466                             setAudioDevice(DEVICE_WIRED_HEADSET);
    467                         }
    468                         break;
    469                     default:
    470                         loge("Invalid state!");
    471                         break;
    472                 }
    473             }
    474         };
    475 
    476         // Note: the intent we register for here is sticky, so it'll tell us
    477         // immediately what the last action was (plugged or unplugged).
    478         // It will enable us to set the speakerphone correctly.
    479         mContext.registerReceiver(mWiredHeadsetReceiver, filter);
    480     }
    481 
    482     /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
    483     private void unregisterForWiredHeadsetIntentBroadcast() {
    484         mContext.unregisterReceiver(mWiredHeadsetReceiver);
    485         mWiredHeadsetReceiver = null;
    486     }
    487 
    488     /**
    489     * Check if Bluetooth device is connected, register Bluetooth receiver
    490     * and start routing to Bluetooth if a device is connected.
    491     * TODO(henrika): currently only supports the detecion part at startup.
    492     */
    493     private void initBluetooth() {
    494         // Bail out if we don't have the required permission.
    495         mHasBluetoothPermission = mContext.checkPermission(
    496             android.Manifest.permission.BLUETOOTH,
    497             Process.myPid(),
    498             Process.myUid()) == PackageManager.PERMISSION_GRANTED;
    499         if (!mHasBluetoothPermission) {
    500             loge("BLUETOOTH permission is missing!");
    501             return;
    502         }
    503 
    504         // To get a BluetoothAdapter representing the local Bluetooth adapter,
    505         // when running on JELLY_BEAN_MR1 (4.2) and below, call the static
    506         // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and
    507         // higher, retrieve it through getSystemService(String) with
    508         // BLUETOOTH_SERVICE.
    509         // Note: Most methods require the BLUETOOTH permission.
    510         BluetoothAdapter btAdapter = null;
    511         if (android.os.Build.VERSION.SDK_INT <=
    512             android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
    513             // Use static method for Android 4.2 and below to get the
    514             // BluetoothAdapter.
    515             btAdapter = BluetoothAdapter.getDefaultAdapter();
    516         } else {
    517             // Use BluetoothManager to get the BluetoothAdapter for
    518             // Android 4.3 and above.
    519             BluetoothManager btManager = (BluetoothManager) mContext.getSystemService(
    520                     Context.BLUETOOTH_SERVICE);
    521             btAdapter = btManager.getAdapter();
    522         }
    523 
    524         if (btAdapter != null &&
    525             // android.bluetooth.BluetoothAdapter.getProfileConnectionState
    526             // requires BLUETOOTH permission.
    527             android.bluetooth.BluetoothProfile.STATE_CONNECTED ==
    528                     btAdapter.getProfileConnectionState(
    529                             android.bluetooth.BluetoothProfile.HEADSET)) {
    530             synchronized (mLock) {
    531                 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true;
    532             }
    533             // TODO(henrika): ensure that we set the active audio
    534             // device to Bluetooth (not trivial).
    535             setAudioDevice(DEVICE_BLUETOOTH_HEADSET);
    536         }
    537     }
    538 
    539     /**
    540      * Changes selection of the currently active audio device.
    541      *
    542      * @param device Specifies the selected audio device.
    543      */
    544     public void setAudioDevice(int device) {
    545         switch (device) {
    546             case DEVICE_BLUETOOTH_HEADSET:
    547                 // TODO(henrika): add support for turning on an routing to
    548                 // BT here.
    549                 if (DEBUG) logd("--- TO BE IMPLEMENTED ---");
    550                 break;
    551             case DEVICE_SPEAKERPHONE:
    552                 // TODO(henrika): turn off BT if required.
    553                 mAudioDeviceState = STATE_SPEAKERPHONE_ON;
    554                 setSpeakerphoneOn(true);
    555                 break;
    556             case DEVICE_WIRED_HEADSET:
    557                 // TODO(henrika): turn off BT if required.
    558                 mAudioDeviceState = STATE_WIRED_HEADSET_ON;
    559                 setSpeakerphoneOn(false);
    560                 break;
    561             case DEVICE_EARPIECE:
    562                 // TODO(henrika): turn off BT if required.
    563                 mAudioDeviceState = STATE_EARPIECE_ON;
    564                 setSpeakerphoneOn(false);
    565                 break;
    566             default:
    567                 loge("Invalid audio device selection!");
    568                 break;
    569         }
    570         reportUpdate();
    571     }
    572 
    573     private int getNumOfAudioDevicesWithLock() {
    574         int count = 0;
    575         for (int i = 0; i < DEVICE_COUNT; ++i) {
    576             if (mAudioDevices[i])
    577                 count++;
    578         }
    579         return count;
    580     }
    581 
    582     /**
    583      * For now, just log the state change but the idea is that we should
    584      * notify a registered state change listener (if any) that there has
    585      * been a change in the state.
    586      * TODO(henrika): add support for state change listener.
    587      */
    588     private void reportUpdate() {
    589         synchronized (mLock) {
    590             List<String> devices = new ArrayList<String>();
    591             for (int i = 0; i < DEVICE_COUNT; ++i) {
    592                 if (mAudioDevices[i])
    593                     devices.add(DEVICE_NAMES[i]);
    594             }
    595             logd("reportUpdate: state=" + mAudioDeviceState
    596                 + ", devices=" + devices);
    597         }
    598     }
    599 
    600     private void logDeviceInfo() {
    601         Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
    602                 " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
    603                 " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
    604     }
    605 
    606     /** Trivial helper method for debug logging */
    607     private static void logd(String msg) {
    608         Log.d(TAG, msg);
    609     }
    610 
    611     /** Trivial helper method for error logging */
    612     private static void loge(String msg) {
    613         Log.e(TAG, msg);
    614     }
    615 
    616     private class SettingsObserver extends ContentObserver {
    617         SettingsObserver() {
    618             super(new Handler());
    619             mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this);
    620         }
    621 
    622         @Override
    623         public void onChange(boolean selfChange) {
    624             super.onChange(selfChange);
    625             int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
    626             nativeSetMute(mNativeAudioManagerAndroid, (volume == 0));
    627         }
    628     }
    629 
    630     private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted);
    631 
    632     private class SettingsObserverThread extends Thread {
    633         SettingsObserverThread() {
    634             super("SettinsObserver");
    635         }
    636 
    637         @Override
    638         public void run() {
    639             // Set this thread up so the handler will work on it.
    640             Looper.prepare();
    641 
    642             synchronized (mSettingsObserverLock) {
    643                 mSettingsObserver = new SettingsObserver();
    644                 mSettingsObserverLock.notify();
    645             }
    646 
    647             // Listen for volume change.
    648             Looper.loop();
    649         }
    650     }
    651 }
    652