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 240884) |
+++ trunk/src/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java (working copy) |
@@ -4,6 +4,8 @@ |
package org.chromium.media; |
+import android.bluetooth.BluetoothAdapter; |
+import android.bluetooth.BluetoothManager; |
import android.content.BroadcastReceiver; |
import android.content.ContentResolver; |
import android.content.Context; |
@@ -18,7 +20,8 @@ |
import android.media.audiofx.AcousticEchoCanceler; |
import android.os.Build; |
import android.os.Handler; |
-import android.os.HandlerThread; |
+import android.os.Looper; |
+import android.os.Process; |
import android.provider.Settings; |
import android.util.Log; |
@@ -54,7 +57,6 @@ |
} |
// 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; |
@@ -81,6 +83,24 @@ |
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. |
@@ -92,18 +112,15 @@ |
private final Context mContext; |
private final long mNativeAudioManagerAndroid; |
- private int mSavedAudioMode = AudioManager.MODE_INVALID; |
- |
+ private boolean mHasBluetoothPermission = false; |
private boolean mIsInitialized = false; |
private boolean mSavedIsSpeakerphoneOn; |
private boolean mSavedIsMicrophoneMute; |
- // Id of the requested audio device. Can only be modified by |
- // call to setDevice(). |
- private int mRequestedAudioDevice = DEVICE_INVALID; |
+ private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED; |
- // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can |
- // be accessed from the main thread and the audio manager thread. |
+ // Lock to protect |mAudioDevices| 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. |
@@ -111,8 +128,9 @@ |
private final ContentResolver mContentResolver; |
private SettingsObserver mSettingsObserver = null; |
- private HandlerThread mSettingsObserverThread = null; |
+ private SettingsObserverThread mSettingsObserverThread = null; |
private int mCurrentVolume; |
+ private final Object mSettingsObserverLock = new Object(); |
// Broadcast receiver for wired headset intent broadcasts. |
private BroadcastReceiver mWiredHeadsetReceiver; |
@@ -139,31 +157,56 @@ |
*/ |
@CalledByNative |
public void init() { |
- if (DEBUG) logd("init"); |
if (mIsInitialized) |
return; |
- for (int i = 0; i < DEVICE_COUNT; ++i) { |
- mAudioDevices[i] = false; |
+ synchronized (mLock) { |
+ 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. |
- if (hasEarpiece()) { |
- mAudioDevices[DEVICE_EARPIECE] = true; |
+ synchronized (mLock) { |
+ if (hasEarpiece()) { |
+ mAudioDevices[DEVICE_EARPIECE] = true; |
+ } |
+ mAudioDevices[DEVICE_SPEAKERPHONE] = 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(); |
- mSettingsObserverThread = new HandlerThread("SettingsObserver"); |
- mSettingsObserverThread.start(); |
- mSettingsObserver = new SettingsObserver( |
- new Handler(mSettingsObserverThread.getLooper())); |
+ // 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(); |
mIsInitialized = true; |
- if (DEBUG) reportUpdate(); |
+ |
+ mSettingsObserverThread = new SettingsObserverThread(); |
+ mSettingsObserverThread.start(); |
+ synchronized (mSettingsObserverLock) { |
+ try { |
+ mSettingsObserverLock.wait(); |
+ } catch (InterruptedException e) { |
+ Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage()); |
+ } |
+ } |
} |
/** |
@@ -172,79 +215,33 @@ |
*/ |
@CalledByNative |
public void close() { |
- if (DEBUG) logd("close"); |
if (!mIsInitialized) |
return; |
- mSettingsObserverThread.quit(); |
- try { |
- mSettingsObserverThread.join(); |
- } catch (InterruptedException e) { |
- logwtf("HandlerThread.join() exception: " + e.getMessage()); |
+ if (mSettingsObserverThread != null) { |
+ mSettingsObserverThread = null; |
} |
- mSettingsObserverThread = null; |
- mContentResolver.unregisterContentObserver(mSettingsObserver); |
- mSettingsObserver = null; |
+ if (mSettingsObserver != 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 |
- 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; |
+ public void setMode(int mode) { |
+ try { |
+ mAudioManager.setMode(mode); |
+ } catch (SecurityException e) { |
+ Log.e(TAG, "setMode exception: " + e.getMessage()); |
+ logDeviceInfo(); |
} |
} |
@@ -253,36 +250,40 @@ |
* |
* @param deviceId Unique device ID (integer converted to string) |
* representing the selected device. This string is empty if the so-called |
- * default device is requested. |
+ * default device is selected. |
*/ |
@CalledByNative |
- 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; |
+ 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); |
} |
- int defaultDevice = selectDefaultDevice(devices); |
- setAudioDevice(defaultDevice); |
- return true; |
+ } 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!"); |
+ } |
} |
- |
- // 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; |
} |
/** |
@@ -292,23 +293,20 @@ |
*/ |
@CalledByNative |
public AudioDeviceName[] getAudioInputDeviceNames() { |
- boolean devices[] = null; |
synchronized (mLock) { |
- 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++; |
+ 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++; |
+ } |
} |
+ logd("getAudioInputDeviceNames: " + devices); |
+ return array; |
} |
- if (DEBUG) logd("getAudioInputDeviceNames: " + list); |
- return array; |
} |
@CalledByNative |
@@ -389,7 +387,7 @@ |
} |
/** Sets the speaker phone mode. */ |
- private void setSpeakerphoneOn(boolean on) { |
+ public void setSpeakerphoneOn(boolean on) { |
boolean wasOn = mAudioManager.isSpeakerphoneOn(); |
if (wasOn == on) { |
return; |
@@ -398,7 +396,7 @@ |
} |
/** Sets the microphone mute state. */ |
- private void setMicrophoneMute(boolean on) { |
+ public void setMicrophoneMute(boolean on) { |
boolean wasMuted = mAudioManager.isMicrophoneMute(); |
if (wasMuted == on) { |
return; |
@@ -407,7 +405,7 @@ |
} |
/** Gets the current microphone mute state. */ |
- private boolean isMicrophoneMute() { |
+ public boolean isMicrophoneMute() { |
return mAudioManager.isMicrophoneMute(); |
} |
@@ -426,9 +424,7 @@ |
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); |
/** |
- * Receiver which handles changes in wired headset availability: |
- * updates the list of devices; |
- * updates the active device if a device selection has been made. |
+ * Receiver which handles changes in wired headset availablilty. |
*/ |
mWiredHeadsetReceiver = new BroadcastReceiver() { |
private static final int STATE_UNPLUGGED = 0; |
@@ -443,14 +439,13 @@ |
return; |
} |
int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
- if (DEBUG) { |
- int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
- String name = intent.getStringExtra("name"); |
- logd("BroadcastReceiver.onReceive: s=" + state |
+ int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
+ String name = intent.getStringExtra("name"); |
+ logd("==> onReceive: s=" + state |
+ ", m=" + microphone |
+ ", n=" + name |
+ ", sb=" + isInitialStickyBroadcast()); |
- } |
+ |
switch (state) { |
case STATE_UNPLUGGED: |
synchronized (mLock) { |
@@ -460,30 +455,27 @@ |
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(); |
- } |
} |
}; |
@@ -500,11 +492,62 @@ |
} |
/** |
+ * 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. |
*/ |
- private void setAudioDevice(int device) { |
+ public void setAudioDevice(int device) { |
switch (device) { |
case DEVICE_BLUETOOTH_HEADSET: |
// TODO(henrika): add support for turning on an routing to |
@@ -513,14 +556,17 @@ |
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: |
@@ -530,58 +576,11 @@ |
reportUpdate(); |
} |
- /** |
- * 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) { |
+ private int getNumOfAudioDevicesWithLock() { |
int count = 0; |
for (int i = 0; i < DEVICE_COUNT; ++i) { |
- if (devices[i]) |
- ++count; |
+ if (mAudioDevices[i]) |
+ count++; |
} |
return count; |
} |
@@ -599,10 +598,8 @@ |
if (mAudioDevices[i]) |
devices.add(DEVICE_NAMES[i]); |
} |
- if (DEBUG) { |
- logd("reportUpdate: requested=" + mRequestedAudioDevice |
- + ", devices=" + devices); |
- } |
+ logd("reportUpdate: state=" + mAudioDeviceState |
+ + ", devices=" + devices); |
} |
} |
@@ -622,26 +619,39 @@ |
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(Handler handler) { |
- super(handler); |
+ SettingsObserver() { |
+ super(new 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(); |
+ } |
+ } |
} |