| Index: media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
|
| diff --git a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
|
| index a60b69018cf97e598308ce0ab34b0eb989ba217f..02bdf2778cfe5b6b73dbdb96a1f8069e114f0cf4 100644
|
| --- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
|
| +++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
|
| @@ -38,7 +38,41 @@ class AudioManagerAndroid {
|
|
|
| // Set to true to enable debug logs. Avoid in production builds.
|
| // NOTE: always check in as false.
|
| - private static final boolean DEBUG = false;
|
| + private static final boolean DEBUG = true;
|
| +
|
| + /**
|
| + * NonThreadSafe is a helper class used to help verify that methods of a
|
| + * class are called from the same thread.
|
| + * Inspired by class in package com.google.android.apps.chrome.utilities.
|
| + * Is only utilized when DEBUG is set to true.
|
| + */
|
| + private static class NonThreadSafe {
|
| + private Long mThreadId = 0L;
|
| +
|
| + public NonThreadSafe() {
|
| + if (DEBUG) {
|
| + ensureThreadIdAssigned();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Checks if the method is called on the valid thread.
|
| + * Assigns the current thread if no thread was assigned.
|
| + */
|
| + public boolean calledOnValidThread() {
|
| + if (DEBUG) {
|
| + ensureThreadIdAssigned();
|
| + return mThreadId.equals(Thread.currentThread().getId());
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + private void ensureThreadIdAssigned() {
|
| + if (DEBUG) {
|
| + if (mThreadId == 0L) mThreadId = Thread.currentThread().getId();
|
| + }
|
| + }
|
| + }
|
|
|
| /** Simple container for device information. */
|
| private static class AudioDeviceName {
|
| @@ -123,6 +157,12 @@ class AudioManagerAndroid {
|
| // call to setDevice().
|
| private int mRequestedAudioDevice = DEVICE_INVALID;
|
|
|
| + // This class should be created, initialized and closed on the main
|
| + // Java thread. In addition, BroadcastReceiver.onReceive should also
|
| + // happen on this thread. We use |mNonThreadSafe| to ensure that this is
|
| + // the case. Only active when |DEBUG| is set to true.
|
| + private final NonThreadSafe mNonThreadSafe = new NonThreadSafe();
|
| +
|
| // 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();
|
| @@ -155,6 +195,7 @@ class AudioManagerAndroid {
|
| }
|
|
|
| private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) {
|
| + logd("@AudioManagerAndroid: " + Thread.currentThread());
|
| mContext = context;
|
| mNativeAudioManagerAndroid = nativeAudioManagerAndroid;
|
| mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
| @@ -165,21 +206,20 @@ class AudioManagerAndroid {
|
| * Saves the initial speakerphone and microphone state.
|
| * Populates the list of available audio devices and registers receivers
|
| * for broadcast intents related to wired headset and Bluetooth devices.
|
| + * TODO(henrika): investigate if it would be possible to move code in
|
| + * init() to the constructor and code in close() to finalize().
|
| */
|
| @CalledByNative
|
| private void init() {
|
| + logd("@init: " + Thread.currentThread());
|
| + checkIfCalledOnValidThread();
|
| if (DEBUG) logd("init");
|
| if (mIsInitialized)
|
| return;
|
|
|
| - for (int i = 0; i < DEVICE_COUNT; ++i) {
|
| - mAudioDevices[i] = false;
|
| - }
|
| -
|
| // Initialize audio device list with things we know is always available.
|
| - if (hasEarpiece()) {
|
| - mAudioDevices[DEVICE_EARPIECE] = true;
|
| - }
|
| + mAudioDevices[DEVICE_EARPIECE] = hasEarpiece();
|
| + mAudioDevices[DEVICE_WIRED_HEADSET] = hasWiredHeadset();
|
| mAudioDevices[DEVICE_SPEAKERPHONE] = true;
|
|
|
| // Register receivers for broadcast intents related to Bluetooth device
|
| @@ -196,6 +236,7 @@ class AudioManagerAndroid {
|
| new Handler(mSettingsObserverThread.getLooper()));
|
|
|
| mIsInitialized = true;
|
| +
|
| if (DEBUG) reportUpdate();
|
| }
|
|
|
| @@ -205,10 +246,15 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private void close() {
|
| + logd("@close: " + Thread.currentThread());
|
| + checkIfCalledOnValidThread();
|
| if (DEBUG) logd("close");
|
| if (!mIsInitialized)
|
| return;
|
|
|
| + unregisterForWiredHeadsetIntentBroadcast();
|
| + unregisterBluetoothIntentsIfNeeded();
|
| +
|
| mSettingsObserverThread.quit();
|
| try {
|
| mSettingsObserverThread.join();
|
| @@ -219,9 +265,6 @@ class AudioManagerAndroid {
|
| mContentResolver.unregisterContentObserver(mSettingsObserver);
|
| mSettingsObserver = null;
|
|
|
| - unregisterForWiredHeadsetIntentBroadcast();
|
| - unregisterBluetoothIntentsIfNeeded();
|
| -
|
| mIsInitialized = false;
|
| }
|
|
|
| @@ -232,6 +275,7 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private void setCommunicationAudioModeOn(boolean on) {
|
| + logd("@setCommunicationAudioModeOn: " + Thread.currentThread());
|
| if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")");
|
|
|
| if (on) {
|
| @@ -291,7 +335,9 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private boolean setDevice(String deviceId) {
|
| + logd("@setDevice: " + Thread.currentThread());
|
| if (DEBUG) logd("setDevice: " + deviceId);
|
| +
|
| int intDeviceId = deviceId.isEmpty() ?
|
| DEVICE_DEFAULT : Integer.parseInt(deviceId);
|
|
|
| @@ -326,6 +372,8 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private AudioDeviceName[] getAudioInputDeviceNames() {
|
| + logd("@getAudioInputDeviceNames: " + Thread.currentThread());
|
| + if (DEBUG) logd("getAudioInputDeviceNames");
|
| boolean devices[] = null;
|
| synchronized (mLock) {
|
| devices = mAudioDevices.clone();
|
| @@ -347,6 +395,7 @@ class AudioManagerAndroid {
|
|
|
| @CalledByNative
|
| private int getNativeOutputSampleRate() {
|
| + logd("@getNativeOutputSampleRate: " + Thread.currentThread());
|
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
| String sampleRateString = mAudioManager.getProperty(
|
| AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
| @@ -365,6 +414,7 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private static int getMinInputFrameSize(int sampleRate, int channels) {
|
| + logd("@getMinInputFrameSize: " + Thread.currentThread());
|
| int channelConfig;
|
| if (channels == 1) {
|
| channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
| @@ -385,6 +435,7 @@ class AudioManagerAndroid {
|
| */
|
| @CalledByNative
|
| private static int getMinOutputFrameSize(int sampleRate, int channels) {
|
| + logd("@getMinOutputFrameSize: " + Thread.currentThread());
|
| int channelConfig;
|
| if (channels == 1) {
|
| channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
| @@ -399,12 +450,14 @@ class AudioManagerAndroid {
|
|
|
| @CalledByNative
|
| private boolean isAudioLowLatencySupported() {
|
| + logd("@isAudioLowLatencySupported: " + Thread.currentThread());
|
| return mContext.getPackageManager().hasSystemFeature(
|
| PackageManager.FEATURE_AUDIO_LOW_LATENCY);
|
| }
|
|
|
| @CalledByNative
|
| private int getAudioLowLatencyOutputFrameSize() {
|
| + logd("@getAudioLowLatencyOutputFrameSize: " + Thread.currentThread());
|
| String framesPerBuffer =
|
| mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
|
| return (framesPerBuffer == null ?
|
| @@ -412,7 +465,8 @@ class AudioManagerAndroid {
|
| }
|
|
|
| @CalledByNative
|
| - public static boolean shouldUseAcousticEchoCanceler() {
|
| + private static boolean shouldUseAcousticEchoCanceler() {
|
| + logd("@shouldUseAcousticEchoCanceler: " + Thread.currentThread());
|
| // AcousticEchoCanceler was added in API level 16 (Jelly Bean).
|
| // Next is a list of device models which have been vetted for good
|
| // quality platform echo cancellation.
|
| @@ -429,6 +483,16 @@ class AudioManagerAndroid {
|
| }
|
|
|
| /**
|
| + * Helper method for debugging purposes. Logs message if method is not
|
| + * called on same thread as this object was created on.
|
| + */
|
| + private void checkIfCalledOnValidThread() {
|
| + if (DEBUG && !mNonThreadSafe.calledOnValidThread()) {
|
| + logwtf("close is not called on valid thread!");
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Register for BT intents if we have the BLUETOOTH permission.
|
| * Also extends the list of available devices with a BT device if one exists.
|
| */
|
| @@ -445,9 +509,7 @@ class AudioManagerAndroid {
|
| if (!mHasBluetoothPermission) {
|
| return;
|
| }
|
| - if (hasBluetoothHeadset()) {
|
| - mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true;
|
| - }
|
| + mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = hasBluetoothHeadset();
|
|
|
| // Register receivers for broadcast intents related to changes in
|
| // Bluetooth headset availability and usage of the SCO channel.
|
| @@ -487,12 +549,24 @@ class AudioManagerAndroid {
|
| return mAudioManager.isMicrophoneMute();
|
| }
|
|
|
| - /** Gets the current earpice state. */
|
| + /** Gets the current earpiece state. */
|
| private boolean hasEarpiece() {
|
| return mContext.getPackageManager().hasSystemFeature(
|
| PackageManager.FEATURE_TELEPHONY);
|
| }
|
|
|
| + /**
|
| + * Checks whether a wired headset is connected or not.
|
| + * This is not a valid indication that audio playback is actually over
|
| + * the wired headset as audio routing depends on other conditions. We
|
| + * only use it as an early indicator (during initialization) of an attached
|
| + * wired headset.
|
| + */
|
| + @Deprecated
|
| + private boolean hasWiredHeadset() {
|
| + return mAudioManager.isWiredHeadsetOn();
|
| + }
|
| +
|
| /** Checks if the process has BLUETOOTH permission or not. */
|
| private boolean hasBluetoothPermission() {
|
| boolean hasBluetooth = mContext.checkPermission(
|
| @@ -559,6 +633,8 @@ class AudioManagerAndroid {
|
|
|
| @Override
|
| public void onReceive(Context context, Intent intent) {
|
| + logd("@onReceive: " + Thread.currentThread());
|
| + checkIfCalledOnValidThread();
|
| int state = intent.getIntExtra("state", STATE_UNPLUGGED);
|
| if (DEBUG) {
|
| int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
|
| @@ -627,6 +703,7 @@ class AudioManagerAndroid {
|
| mBluetoothHeadsetReceiver = new BroadcastReceiver() {
|
| @Override
|
| public void onReceive(Context context, Intent intent) {
|
| + checkIfCalledOnValidThread();
|
| // A change in connection state of the Headset profile has
|
| // been detected, e.g. BT headset has been connected or
|
| // disconnected. This broadcast is *not* sticky.
|
|
|