| Index: trunk/src/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java | 
| =================================================================== | 
| --- trunk/src/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java	(revision 240886) | 
| +++ trunk/src/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java	(working copy) | 
| @@ -4,8 +4,6 @@ | 
|  | 
| package org.chromium.media; | 
|  | 
| -import android.bluetooth.BluetoothAdapter; | 
| -import android.bluetooth.BluetoothManager; | 
| import android.content.BroadcastReceiver; | 
| import android.content.ContentResolver; | 
| import android.content.Context; | 
| @@ -20,8 +18,7 @@ | 
| import android.media.audiofx.AcousticEchoCanceler; | 
| import android.os.Build; | 
| import android.os.Handler; | 
| -import android.os.Looper; | 
| -import android.os.Process; | 
| +import android.os.HandlerThread; | 
| import android.provider.Settings; | 
| import android.util.Log; | 
|  | 
| @@ -57,6 +54,7 @@ | 
| } | 
|  | 
| // Supported audio device types. | 
| +    private static final int DEVICE_DEFAULT = -2; | 
| private static final int DEVICE_INVALID = -1; | 
| private static final int DEVICE_SPEAKERPHONE = 0; | 
| private static final int DEVICE_WIRED_HEADSET = 1; | 
| @@ -83,24 +81,6 @@ | 
| DEVICE_BLUETOOTH_HEADSET, | 
| }; | 
|  | 
| -    // The device does not have any audio device. | 
| -    static final int STATE_NO_DEVICE_SELECTED = 0; | 
| -    // The speakerphone is on and an associated microphone is used. | 
| -    static final int STATE_SPEAKERPHONE_ON = 1; | 
| -    // The phone's earpiece is on and an associated microphone is used. | 
| -    static final int STATE_EARPIECE_ON = 2; | 
| -    // A wired headset (with or without a microphone) is plugged in. | 
| -    static final int STATE_WIRED_HEADSET_ON = 3; | 
| -    // The audio stream is being directed to a Bluetooth headset. | 
| -    static final int STATE_BLUETOOTH_ON = 4; | 
| -    // We've requested that the audio stream be directed to Bluetooth, but | 
| -    // have not yet received a response from the framework. | 
| -    static final int STATE_BLUETOOTH_TURNING_ON = 5; | 
| -    // We've requested that the audio stream stop being directed to | 
| -    // Bluetooth, but have not yet received a response from the framework. | 
| -    static final int STATE_BLUETOOTH_TURNING_OFF = 6; | 
| -    // TODO(henrika): document the valid state transitions. | 
| - | 
| // Use 44.1kHz as the default sampling rate. | 
| private static final int DEFAULT_SAMPLING_RATE = 44100; | 
| // Randomly picked up frame size which is close to return value on N4. | 
| @@ -112,15 +92,18 @@ | 
| private final Context mContext; | 
| private final long mNativeAudioManagerAndroid; | 
|  | 
| -    private boolean mHasBluetoothPermission = false; | 
| +    private int mSavedAudioMode = AudioManager.MODE_INVALID; | 
| + | 
| private boolean mIsInitialized = false; | 
| private boolean mSavedIsSpeakerphoneOn; | 
| private boolean mSavedIsMicrophoneMute; | 
|  | 
| -    private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED; | 
| +    // Id of the requested audio device. Can only be modified by | 
| +    // call to setDevice(). | 
| +    private int mRequestedAudioDevice = DEVICE_INVALID; | 
|  | 
| -    // Lock to protect |mAudioDevices| which can be accessed from the main | 
| -    // thread and the audio manager thread. | 
| +    // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can | 
| +    // be accessed from the main thread and the audio manager thread. | 
| private final Object mLock = new Object(); | 
|  | 
| // Contains a list of currently available audio devices. | 
| @@ -128,9 +111,8 @@ | 
|  | 
| private final ContentResolver mContentResolver; | 
| private SettingsObserver mSettingsObserver = null; | 
| -    private SettingsObserverThread mSettingsObserverThread = null; | 
| +    private HandlerThread mSettingsObserverThread = null; | 
| private int mCurrentVolume; | 
| -    private final Object mSettingsObserverLock = new Object(); | 
|  | 
| // Broadcast receiver for wired headset intent broadcasts. | 
| private BroadcastReceiver mWiredHeadsetReceiver; | 
| @@ -157,56 +139,31 @@ | 
| */ | 
| @CalledByNative | 
| public void init() { | 
| +        if (DEBUG) logd("init"); | 
| if (mIsInitialized) | 
| return; | 
|  | 
| -        synchronized (mLock) { | 
| -            for (int i = 0; i < DEVICE_COUNT; ++i) { | 
| -                mAudioDevices[i] = false; | 
| -            } | 
| +        for (int i = 0; i < DEVICE_COUNT; ++i) { | 
| +            mAudioDevices[i] = false; | 
| } | 
|  | 
| -        // Store microphone mute state and speakerphone state so it can | 
| -        // be restored when closing. | 
| -        mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn(); | 
| -        mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute(); | 
| - | 
| -        // Always enable speaker phone by default. This state might be reset | 
| -        // by the wired headset receiver when it gets its initial sticky | 
| -        // intent, if any. | 
| -        setSpeakerphoneOn(true); | 
| -        mAudioDeviceState = STATE_SPEAKERPHONE_ON; | 
| - | 
| // Initialize audio device list with things we know is always available. | 
| -        synchronized (mLock) { | 
| -            if (hasEarpiece()) { | 
| -                mAudioDevices[DEVICE_EARPIECE] = true; | 
| -            } | 
| -            mAudioDevices[DEVICE_SPEAKERPHONE] = true; | 
| +        if (hasEarpiece()) { | 
| +            mAudioDevices[DEVICE_EARPIECE] = true; | 
| } | 
| +        mAudioDevices[DEVICE_SPEAKERPHONE] = true; | 
|  | 
| // Register receiver for broadcasted intents related to adding/ | 
| // removing a wired headset (Intent.ACTION_HEADSET_PLUG). | 
| -        // Also starts routing to the wired headset/headphone if one is | 
| -        // already attached (can be overridden by a Bluetooth headset). | 
| registerForWiredHeadsetIntentBroadcast(); | 
|  | 
| -        // Start routing to Bluetooth if there's a connected device. | 
| -        // TODO(henrika): the actual routing part is not implemented yet. | 
| -        // All we do currently is to detect if BT headset is attached or not. | 
| -        initBluetooth(); | 
| +        mSettingsObserverThread = new HandlerThread("SettingsObserver"); | 
| +        mSettingsObserverThread.start(); | 
| +        mSettingsObserver = new SettingsObserver( | 
| +            new Handler(mSettingsObserverThread.getLooper())); | 
|  | 
| mIsInitialized = true; | 
| - | 
| -        mSettingsObserverThread = new SettingsObserverThread(); | 
| -        mSettingsObserverThread.start(); | 
| -        synchronized (mSettingsObserverLock) { | 
| -            try { | 
| -                mSettingsObserverLock.wait(); | 
| -            } catch (InterruptedException e) { | 
| -                Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage()); | 
| -            } | 
| -        } | 
| +        if (DEBUG) reportUpdate(); | 
| } | 
|  | 
| /** | 
| @@ -215,33 +172,79 @@ | 
| */ | 
| @CalledByNative | 
| public void close() { | 
| +        if (DEBUG) logd("close"); | 
| if (!mIsInitialized) | 
| return; | 
|  | 
| -        if (mSettingsObserverThread != null) { | 
| -            mSettingsObserverThread = null; | 
| +        mSettingsObserverThread.quit(); | 
| +        try { | 
| +            mSettingsObserverThread.join(); | 
| +        } catch (InterruptedException e) { | 
| +            logwtf("HandlerThread.join() exception: " + e.getMessage()); | 
| } | 
| -        if (mSettingsObserver != null) { | 
| -            mContentResolver.unregisterContentObserver(mSettingsObserver); | 
| -            mSettingsObserver = null; | 
| -        } | 
| +        mSettingsObserverThread = null; | 
| +        mContentResolver.unregisterContentObserver(mSettingsObserver); | 
| +        mSettingsObserver = null; | 
|  | 
| unregisterForWiredHeadsetIntentBroadcast(); | 
|  | 
| -        // Restore previously stored audio states. | 
| -        setMicrophoneMute(mSavedIsMicrophoneMute); | 
| -        setSpeakerphoneOn(mSavedIsSpeakerphoneOn); | 
| - | 
| mIsInitialized = false; | 
| } | 
|  | 
| +    /** | 
| +     * Saves current audio mode and sets audio mode to MODE_IN_COMMUNICATION | 
| +     * if input parameter is true. Restores saved audio mode if input parameter | 
| +     * is false. | 
| +     */ | 
| @CalledByNative | 
| -    public void setMode(int mode) { | 
| -        try { | 
| -            mAudioManager.setMode(mode); | 
| -        } catch (SecurityException e) { | 
| -            Log.e(TAG, "setMode exception: " + e.getMessage()); | 
| -            logDeviceInfo(); | 
| +    private void setCommunicationAudioModeOn(boolean on) { | 
| +        if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")"); | 
| + | 
| +        if (on) { | 
| +            if (mSavedAudioMode != AudioManager.MODE_INVALID) { | 
| +                logwtf("Audio mode has already been set!"); | 
| +                return; | 
| +            } | 
| + | 
| +            // Store the current audio mode the first time we try to | 
| +            // switch to communication mode. | 
| +            try { | 
| +                mSavedAudioMode = mAudioManager.getMode(); | 
| +            } catch (SecurityException e) { | 
| +                logwtf("getMode exception: " + e.getMessage()); | 
| +                logDeviceInfo(); | 
| +            } | 
| + | 
| +            // Store microphone mute state and speakerphone state so it can | 
| +            // be restored when closing. | 
| +            mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn(); | 
| +            mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute(); | 
| + | 
| +            try { | 
| +                mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); | 
| +            } catch (SecurityException e) { | 
| +                logwtf("setMode exception: " + e.getMessage()); | 
| +                logDeviceInfo(); | 
| +            } | 
| +        } else { | 
| +            if (mSavedAudioMode == AudioManager.MODE_INVALID) { | 
| +                logwtf("Audio mode has not yet been set!"); | 
| +                return; | 
| +            } | 
| + | 
| +            // Restore previously stored audio states. | 
| +            setMicrophoneMute(mSavedIsMicrophoneMute); | 
| +            setSpeakerphoneOn(mSavedIsSpeakerphoneOn); | 
| + | 
| +            // Restore the mode that was used before we switched to | 
| +            // communication mode. | 
| +            try { | 
| +                mAudioManager.setMode(mSavedAudioMode); | 
| +            } catch (SecurityException e) { | 
| +                logwtf("setMode exception: " + e.getMessage()); | 
| +                logDeviceInfo(); | 
| +            } | 
| +            mSavedAudioMode = AudioManager.MODE_INVALID; | 
| } | 
| } | 
|  | 
| @@ -250,40 +253,36 @@ | 
| * | 
| * @param deviceId Unique device ID (integer converted to string) | 
| * representing the selected device. This string is empty if the so-called | 
| -     * default device is selected. | 
| +     * default device is requested. | 
| */ | 
| @CalledByNative | 
| -    public void setDevice(String deviceId) { | 
| -        boolean devices[] = null; | 
| -        synchronized (mLock) { | 
| -            devices = mAudioDevices.clone(); | 
| -        } | 
| -        if (deviceId.isEmpty()) { | 
| -            logd("setDevice: default"); | 
| -            // Use a special selection scheme if the default device is selected. | 
| -            // The "most unique" device will be selected; Bluetooth first, then | 
| -            // wired headset and last the speaker phone. | 
| -            if (devices[DEVICE_BLUETOOTH_HEADSET]) { | 
| -                // TODO(henrika): possibly need improvements here if we are | 
| -                // in a STATE_BLUETOOTH_TURNING_OFF state. | 
| -                setAudioDevice(DEVICE_BLUETOOTH_HEADSET); | 
| -            } else if (devices[DEVICE_WIRED_HEADSET]) { | 
| -                setAudioDevice(DEVICE_WIRED_HEADSET); | 
| -            } else { | 
| -                setAudioDevice(DEVICE_SPEAKERPHONE); | 
| +    private boolean setDevice(String deviceId) { | 
| +        if (DEBUG) logd("setDevice: " + deviceId); | 
| +        int intDeviceId = deviceId.isEmpty() ? | 
| +            DEVICE_DEFAULT : Integer.parseInt(deviceId); | 
| + | 
| +        if (intDeviceId == DEVICE_DEFAULT) { | 
| +            boolean devices[] = null; | 
| +            synchronized (mLock) { | 
| +                devices = mAudioDevices.clone(); | 
| +                mRequestedAudioDevice = DEVICE_DEFAULT; | 
| } | 
| -        } else { | 
| -            logd("setDevice: " + deviceId); | 
| -            // A non-default device is specified. Verify that it is valid | 
| -            // device, and if so, start using it. | 
| -            List<Integer> validIds = Arrays.asList(VALID_DEVICES); | 
| -            Integer id = Integer.valueOf(deviceId); | 
| -            if (validIds.contains(id)) { | 
| -                setAudioDevice(id.intValue()); | 
| -            } else { | 
| -                loge("Invalid device ID!"); | 
| -            } | 
| +            int defaultDevice = selectDefaultDevice(devices); | 
| +            setAudioDevice(defaultDevice); | 
| +            return true; | 
| } | 
| + | 
| +        // A non-default device is specified. Verify that it is valid | 
| +        // device, and if so, start using it. | 
| +        List<Integer> validIds = Arrays.asList(VALID_DEVICES); | 
| +        if (!validIds.contains(intDeviceId) || !mAudioDevices[intDeviceId]) { | 
| +            return false; | 
| +        } | 
| +        synchronized (mLock) { | 
| +            mRequestedAudioDevice = intDeviceId; | 
| +        } | 
| +        setAudioDevice(intDeviceId); | 
| +        return true; | 
| } | 
|  | 
| /** | 
| @@ -293,20 +292,23 @@ | 
| */ | 
| @CalledByNative | 
| public AudioDeviceName[] getAudioInputDeviceNames() { | 
| +        boolean devices[] = null; | 
| synchronized (mLock) { | 
| -            List<String> devices = new ArrayList<String>(); | 
| -            AudioDeviceName[] array = new AudioDeviceName[getNumOfAudioDevicesWithLock()]; | 
| -            int i = 0; | 
| -            for (int id = 0; id < DEVICE_COUNT; ++id) { | 
| -                if (mAudioDevices[id]) { | 
| -                    array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); | 
| -                    devices.add(DEVICE_NAMES[id]); | 
| -                    i++; | 
| -                } | 
| +            devices = mAudioDevices.clone(); | 
| +        } | 
| +        List<String> list = new ArrayList<String>(); | 
| +        AudioDeviceName[] array = | 
| +            new AudioDeviceName[getNumOfAudioDevices(devices)]; | 
| +        int i = 0; | 
| +        for (int id = 0; id < DEVICE_COUNT; ++id) { | 
| +            if (devices[id]) { | 
| +                array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); | 
| +                list.add(DEVICE_NAMES[id]); | 
| +                i++; | 
| } | 
| -            logd("getAudioInputDeviceNames: " + devices); | 
| -            return array; | 
| } | 
| +        if (DEBUG) logd("getAudioInputDeviceNames: " + list); | 
| +        return array; | 
| } | 
|  | 
| @CalledByNative | 
| @@ -387,7 +389,7 @@ | 
| } | 
|  | 
| /** Sets the speaker phone mode. */ | 
| -    public void setSpeakerphoneOn(boolean on) { | 
| +    private void setSpeakerphoneOn(boolean on) { | 
| boolean wasOn = mAudioManager.isSpeakerphoneOn(); | 
| if (wasOn == on) { | 
| return; | 
| @@ -396,7 +398,7 @@ | 
| } | 
|  | 
| /** Sets the microphone mute state. */ | 
| -    public void setMicrophoneMute(boolean on) { | 
| +    private void setMicrophoneMute(boolean on) { | 
| boolean wasMuted = mAudioManager.isMicrophoneMute(); | 
| if (wasMuted == on) { | 
| return; | 
| @@ -405,7 +407,7 @@ | 
| } | 
|  | 
| /** Gets  the current microphone mute state. */ | 
| -    public boolean isMicrophoneMute() { | 
| +    private boolean isMicrophoneMute() { | 
| return mAudioManager.isMicrophoneMute(); | 
| } | 
|  | 
| @@ -424,7 +426,9 @@ | 
| IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); | 
|  | 
| /** | 
| -         * Receiver which handles changes in wired headset availablilty. | 
| +         * Receiver which handles changes in wired headset availability: | 
| +         *   updates the list of devices; | 
| +         *   updates the active device if a device selection has been made. | 
| */ | 
| mWiredHeadsetReceiver = new BroadcastReceiver() { | 
| private static final int STATE_UNPLUGGED = 0; | 
| @@ -439,13 +443,14 @@ | 
| return; | 
| } | 
| int state = intent.getIntExtra("state", STATE_UNPLUGGED); | 
| -                int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); | 
| -                String name = intent.getStringExtra("name"); | 
| -                logd("==> onReceive: s=" + state | 
| +                if (DEBUG) { | 
| +                    int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); | 
| +                    String name = intent.getStringExtra("name"); | 
| +                    logd("BroadcastReceiver.onReceive: s=" + state | 
| + ", m=" + microphone | 
| + ", n=" + name | 
| + ", sb=" + isInitialStickyBroadcast()); | 
| - | 
| +                } | 
| switch (state) { | 
| case STATE_UNPLUGGED: | 
| synchronized (mLock) { | 
| @@ -455,27 +460,30 @@ | 
| mAudioDevices[DEVICE_EARPIECE] = true; | 
| } | 
| } | 
| -                        // If wired headset was used before it was unplugged, | 
| -                        // switch to speaker phone. If it was not in use; just | 
| -                        // log the change. | 
| -                        if (mAudioDeviceState == STATE_WIRED_HEADSET_ON) { | 
| -                            setAudioDevice(DEVICE_SPEAKERPHONE); | 
| -                        } else { | 
| -                            reportUpdate(); | 
| -                        } | 
| break; | 
| case STATE_PLUGGED: | 
| synchronized (mLock) { | 
| // Wired headset and earpiece are mutually exclusive. | 
| mAudioDevices[DEVICE_WIRED_HEADSET] = true; | 
| mAudioDevices[DEVICE_EARPIECE] = false; | 
| -                            setAudioDevice(DEVICE_WIRED_HEADSET); | 
| } | 
| break; | 
| default: | 
| loge("Invalid state!"); | 
| break; | 
| } | 
| + | 
| +                // Update the existing device selection, but only if a specific | 
| +                // device has already been selected explicitly. | 
| +                boolean deviceHasBeenRequested = false; | 
| +                synchronized (mLock) { | 
| +                    deviceHasBeenRequested = (mRequestedAudioDevice != DEVICE_INVALID); | 
| +                } | 
| +                if (deviceHasBeenRequested) { | 
| +                    updateDeviceActivation(); | 
| +                } else if (DEBUG) { | 
| +                    reportUpdate(); | 
| +                } | 
| } | 
| }; | 
|  | 
| @@ -492,62 +500,11 @@ | 
| } | 
|  | 
| /** | 
| -    * Check if Bluetooth device is connected, register Bluetooth receiver | 
| -    * and start routing to Bluetooth if a device is connected. | 
| -    * TODO(henrika): currently only supports the detecion part at startup. | 
| -    */ | 
| -    private void initBluetooth() { | 
| -        // Bail out if we don't have the required permission. | 
| -        mHasBluetoothPermission = mContext.checkPermission( | 
| -            android.Manifest.permission.BLUETOOTH, | 
| -            Process.myPid(), | 
| -            Process.myUid()) == PackageManager.PERMISSION_GRANTED; | 
| -        if (!mHasBluetoothPermission) { | 
| -            loge("BLUETOOTH permission is missing!"); | 
| -            return; | 
| -        } | 
| - | 
| -        // To get a BluetoothAdapter representing the local Bluetooth adapter, | 
| -        // when running on JELLY_BEAN_MR1 (4.2) and below, call the static | 
| -        // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and | 
| -        // higher, retrieve it through getSystemService(String) with | 
| -        // BLUETOOTH_SERVICE. | 
| -        // Note: Most methods require the BLUETOOTH permission. | 
| -        BluetoothAdapter btAdapter = null; | 
| -        if (android.os.Build.VERSION.SDK_INT <= | 
| -            android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { | 
| -            // Use static method for Android 4.2 and below to get the | 
| -            // BluetoothAdapter. | 
| -            btAdapter = BluetoothAdapter.getDefaultAdapter(); | 
| -        } else { | 
| -            // Use BluetoothManager to get the BluetoothAdapter for | 
| -            // Android 4.3 and above. | 
| -            BluetoothManager btManager = (BluetoothManager) mContext.getSystemService( | 
| -                    Context.BLUETOOTH_SERVICE); | 
| -            btAdapter = btManager.getAdapter(); | 
| -        } | 
| - | 
| -        if (btAdapter != null && | 
| -            // android.bluetooth.BluetoothAdapter.getProfileConnectionState | 
| -            // requires BLUETOOTH permission. | 
| -            android.bluetooth.BluetoothProfile.STATE_CONNECTED == | 
| -                    btAdapter.getProfileConnectionState( | 
| -                            android.bluetooth.BluetoothProfile.HEADSET)) { | 
| -            synchronized (mLock) { | 
| -                mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true; | 
| -            } | 
| -            // TODO(henrika): ensure that we set the active audio | 
| -            // device to Bluetooth (not trivial). | 
| -            setAudioDevice(DEVICE_BLUETOOTH_HEADSET); | 
| -        } | 
| -    } | 
| - | 
| -    /** | 
| * Changes selection of the currently active audio device. | 
| * | 
| * @param device Specifies the selected audio device. | 
| */ | 
| -    public void setAudioDevice(int device) { | 
| +    private void setAudioDevice(int device) { | 
| switch (device) { | 
| case DEVICE_BLUETOOTH_HEADSET: | 
| // TODO(henrika): add support for turning on an routing to | 
| @@ -556,17 +513,14 @@ | 
| break; | 
| case DEVICE_SPEAKERPHONE: | 
| // TODO(henrika): turn off BT if required. | 
| -                mAudioDeviceState = STATE_SPEAKERPHONE_ON; | 
| setSpeakerphoneOn(true); | 
| break; | 
| case DEVICE_WIRED_HEADSET: | 
| // TODO(henrika): turn off BT if required. | 
| -                mAudioDeviceState = STATE_WIRED_HEADSET_ON; | 
| setSpeakerphoneOn(false); | 
| break; | 
| case DEVICE_EARPIECE: | 
| // TODO(henrika): turn off BT if required. | 
| -                mAudioDeviceState = STATE_EARPIECE_ON; | 
| setSpeakerphoneOn(false); | 
| break; | 
| default: | 
| @@ -576,11 +530,58 @@ | 
| reportUpdate(); | 
| } | 
|  | 
| -    private int getNumOfAudioDevicesWithLock() { | 
| +    /** | 
| +     * Use a special selection scheme if the default device is selected. | 
| +     * The "most unique" device will be selected; Wired headset first, | 
| +     * then Bluetooth and last the speaker phone. | 
| +     */ | 
| +    private static int selectDefaultDevice(boolean[] devices) { | 
| +        if (devices[DEVICE_WIRED_HEADSET]) { | 
| +            return DEVICE_WIRED_HEADSET; | 
| +        } else if (devices[DEVICE_BLUETOOTH_HEADSET]) { | 
| +            // TODO(henrika): possibly need improvements here if we are | 
| +            // in a state where Bluetooth is turning off. | 
| +            return DEVICE_BLUETOOTH_HEADSET; | 
| +        } | 
| +        return DEVICE_SPEAKERPHONE; | 
| +    } | 
| + | 
| +    /** | 
| +     * Updates the active device given the current list of devices and | 
| +     * information about if a specific device has been selected or if | 
| +     * the default device is selected. | 
| +     */ | 
| +    private void updateDeviceActivation() { | 
| +        boolean devices[] = null; | 
| +        int requested = DEVICE_INVALID; | 
| +        synchronized (mLock) { | 
| +            requested = mRequestedAudioDevice; | 
| +            devices = mAudioDevices.clone(); | 
| +        } | 
| +        if (requested == DEVICE_INVALID) { | 
| +            loge("Unable to activate device since no device is selected!"); | 
| +            return; | 
| +        } | 
| + | 
| +        // Update default device if it has been selected explicitly, or | 
| +        // the selected device has been removed from the list. | 
| +        if (requested == DEVICE_DEFAULT || !devices[requested]) { | 
| +            // Get default device given current list and activate the device. | 
| +            int defaultDevice = selectDefaultDevice(devices); | 
| +            setAudioDevice(defaultDevice); | 
| +        } else { | 
| +            // Activate the selected device since we know that it exists in | 
| +            // the list. | 
| +            setAudioDevice(requested); | 
| +        } | 
| +    } | 
| + | 
| +    /** Returns number of available devices */ | 
| +    private static int getNumOfAudioDevices(boolean[] devices) { | 
| int count = 0; | 
| for (int i = 0; i < DEVICE_COUNT; ++i) { | 
| -            if (mAudioDevices[i]) | 
| -                count++; | 
| +            if (devices[i]) | 
| +                ++count; | 
| } | 
| return count; | 
| } | 
| @@ -598,8 +599,10 @@ | 
| if (mAudioDevices[i]) | 
| devices.add(DEVICE_NAMES[i]); | 
| } | 
| -            logd("reportUpdate: state=" + mAudioDeviceState | 
| -                + ", devices=" + devices); | 
| +            if (DEBUG) { | 
| +                logd("reportUpdate: requested=" + mRequestedAudioDevice | 
| +                    + ", devices=" + devices); | 
| +            } | 
| } | 
| } | 
|  | 
| @@ -619,39 +622,26 @@ | 
| Log.e(TAG, msg); | 
| } | 
|  | 
| +    /** What a Terrible Failure: Reports a condition that should never happen */ | 
| +    private void logwtf(String msg) { | 
| +        Log.wtf(TAG, msg); | 
| +    } | 
| + | 
| private class SettingsObserver extends ContentObserver { | 
| -        SettingsObserver() { | 
| -            super(new Handler()); | 
| +        SettingsObserver(Handler handler) { | 
| +            super(handler); | 
| mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this); | 
| } | 
|  | 
| @Override | 
| public void onChange(boolean selfChange) { | 
| +            if (DEBUG) logd("SettingsObserver.onChange: " + selfChange); | 
| super.onChange(selfChange); | 
| int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL); | 
| +            if (DEBUG) logd("nativeSetMute: " + (volume == 0)); | 
| nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); | 
| } | 
| } | 
|  | 
| private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); | 
| - | 
| -    private class SettingsObserverThread extends Thread { | 
| -        SettingsObserverThread() { | 
| -            super("SettinsObserver"); | 
| -        } | 
| - | 
| -        @Override | 
| -        public void run() { | 
| -            // Set this thread up so the handler will work on it. | 
| -            Looper.prepare(); | 
| - | 
| -            synchronized (mSettingsObserverLock) { | 
| -                mSettingsObserver = new SettingsObserver(); | 
| -                mSettingsObserverLock.notify(); | 
| -            } | 
| - | 
| -            // Listen for volume change. | 
| -            Looper.loop(); | 
| -        } | 
| -    } | 
| } | 
|  |