Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.media; | 5 package org.chromium.media; |
| 6 | 6 |
| 7 import android.bluetooth.BluetoothAdapter; | |
| 8 import android.bluetooth.BluetoothManager; | |
| 7 import android.content.BroadcastReceiver; | 9 import android.content.BroadcastReceiver; |
| 8 import android.content.Context; | 10 import android.content.Context; |
| 9 import android.content.Intent; | 11 import android.content.Intent; |
| 10 import android.content.IntentFilter; | 12 import android.content.IntentFilter; |
| 11 import android.content.pm.PackageManager; | 13 import android.content.pm.PackageManager; |
| 12 import android.media.AudioFormat; | 14 import android.media.AudioFormat; |
| 13 import android.media.AudioManager; | 15 import android.media.AudioManager; |
| 14 import android.media.AudioRecord; | 16 import android.media.AudioRecord; |
| 15 import android.media.AudioTrack; | 17 import android.media.AudioTrack; |
| 16 import android.os.Build; | 18 import android.os.Build; |
| 17 import android.util.Log; | 19 import android.util.Log; |
| 18 | 20 |
| 21 import java.util.Arrays; | |
| 22 import java.util.ArrayList; | |
| 23 import java.util.HashSet; | |
| 24 import java.util.List; | |
| 25 import java.util.Set; | |
| 26 | |
| 19 import org.chromium.base.CalledByNative; | 27 import org.chromium.base.CalledByNative; |
| 20 import org.chromium.base.JNINamespace; | 28 import org.chromium.base.JNINamespace; |
| 21 | 29 |
| 22 @JNINamespace("media") | 30 @JNINamespace("media") |
| 23 class AudioManagerAndroid { | 31 class AudioManagerAndroid { |
| 24 private static final String TAG = "AudioManagerAndroid"; | 32 private static final String TAG = "AudioManagerAndroid"; |
| 25 | 33 |
| 34 /** | |
| 35 * Lists the possible types (and names) of audio device that we support | |
| 36 * on Android. | |
| 37 */ | |
| 38 private enum AudioDevice { | |
| 39 SPEAKERPHONE("Speaker"), | |
| 40 WIRED_HEADSET("Wired headphones"), | |
| 41 EARPIECE("Handset earpice"), | |
| 42 BLUETOOTH_HEADSET("Bluetooth headset"); | |
| 43 | |
| 44 private final String name; | |
| 45 | |
| 46 private AudioDevice(String s) { | |
| 47 name = s; | |
| 48 } | |
| 49 | |
| 50 @Override | |
| 51 public String toString() { | |
| 52 return name; | |
| 53 } | |
| 54 | |
| 55 public AudioDevice fromString(String name) { | |
| 56 if (name == null) | |
| 57 return null; | |
| 58 for (AudioDevice a : AudioDevice.values()) { | |
| 59 if (name.equalsIgnoreCase(a.name)) { | |
| 60 return a; | |
| 61 } | |
| 62 } | |
| 63 return null; | |
| 64 } | |
| 65 } | |
| 66 | |
| 26 // Most of Google lead devices use 44.1K as the default sampling rate, 44.1K | 67 // Most of Google lead devices use 44.1K as the default sampling rate, 44.1K |
| 27 // is also widely used on other android devices. | 68 // is also widely used on other android devices. |
| 28 private static final int DEFAULT_SAMPLING_RATE = 44100; | 69 private static final int DEFAULT_SAMPLING_RATE = 44100; |
| 29 // Randomly picked up frame size which is close to return value on N4. | 70 // Randomly picked up frame size which is close to return value on N4. |
| 30 // Return this default value when | 71 // Return this default value when |
| 31 // getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) fails. | 72 // getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) fails. |
| 32 private static final int DEFAULT_FRAME_PER_BUFFER = 256; | 73 private static final int DEFAULT_FRAME_PER_BUFFER = 256; |
| 33 | 74 |
| 75 private boolean mIsInitialized = false; | |
| 76 | |
| 34 private final AudioManager mAudioManager; | 77 private final AudioManager mAudioManager; |
| 35 private final Context mContext; | 78 private final Context mContext; |
| 36 | 79 |
| 37 private BroadcastReceiver mReceiver; | 80 private BroadcastReceiver mReceiver; |
| 38 private boolean mOriginalSpeakerStatus; | 81 private boolean mOriginalSpeakerStatus; |
| 39 | 82 |
| 83 // Use Set to ensure that we don't store duplicates. Ignores order. | |
| 84 private Set<AudioDevice> mAudioDevices = new HashSet<AudioDevice>(); | |
| 85 | |
| 86 /** | |
| 87 * Class to handle changes in wired headset availablilty. | |
| 88 * | |
| 89 * Note that isInitialStickyBroadcast() returns true if the receiver is | |
| 90 * currently processing the initial value of a sticky broadcast. It can | |
| 91 * happen during startup when the cached state us pushed out to us an | |
| 92 * initial "sticky broadcast". | |
| 93 */ | |
| 94 private BroadcastReceiver mWiredHeadsetReceiver = new BroadcastReceiver() { | |
| 95 private static final int STATE_UNPLUGGED = 0; | |
| 96 private static final int STATE_PLUGGED = 1; | |
| 97 | |
| 98 @Override | |
| 99 public void onReceive(Context context, Intent intent) { | |
| 100 String action = intent.getAction(); | |
| 101 if (action.equals(Intent.ACTION_HEADSET_PLUG)) { | |
| 102 int state = intent.getIntExtra("state", STATE_UNPLUGGED); | |
| 103 | |
| 104 logd("==> WiredHeadsetReceiver.onReceive: state=" + state | |
| 105 + ", isInitialStickyBroadcast=" | |
| 106 + isInitialStickyBroadcast()); | |
| 107 | |
| 108 switch (state) { | |
| 109 case STATE_UNPLUGGED: | |
| 110 // Wired headset and earpiece are mutually-exclusive. | |
| 111 mAudioDevices.remove(AudioDevice.WIRED_HEADSET); | |
| 112 if (hasEarpiece()) { | |
| 113 mAudioDevices.add(AudioDevice.EARPIECE); | |
| 114 } | |
| 115 // TODO(henrika): check if wired headset was used before | |
| 116 // it was unlpugged. If so, switch to speaker phone. | |
|
Jói
2013/11/25 13:09:35
unlpugged -> unplugged
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
| |
| 117 // If'it was not in use, don't do anything. | |
|
Jói
2013/11/25 13:09:35
If'it -> If it
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
| |
| 118 // Also, turn off BT if it was active at this stage. | |
| 119 break; | |
| 120 case STATE_PLUGGED: | |
| 121 // Wired headset and earpiece are mutually-exclusive. | |
| 122 mAudioDevices.add(AudioDevice.WIRED_HEADSET); | |
| 123 mAudioDevices.remove(AudioDevice.EARPIECE); | |
| 124 // TODO(henrika): ensure that the wired headset is active. | |
| 125 // Possibly turn BT off and also disable the speaker phone. | |
| 126 break; | |
| 127 } | |
| 128 } | |
| 129 } | |
| 130 }; | |
| 131 | |
| 40 @CalledByNative | 132 @CalledByNative |
| 41 public void setMode(int mode) { | 133 public void setMode(int mode) { |
| 42 try { | 134 try { |
| 43 mAudioManager.setMode(mode); | 135 mAudioManager.setMode(mode); |
| 44 } catch (SecurityException e) { | 136 } catch (SecurityException e) { |
| 45 Log.e(TAG, "setMode exception: " + e.getMessage()); | 137 Log.e(TAG, "setMode exception: " + e.getMessage()); |
| 46 logDeviceInfo(); | 138 logDeviceInfo(); |
| 47 } | 139 } |
| 48 } | 140 } |
| 49 | 141 |
| 50 @CalledByNative | 142 @CalledByNative |
| 51 private static AudioManagerAndroid createAudioManagerAndroid(Context context ) { | 143 private static AudioManagerAndroid createAudioManagerAndroid(Context context ) { |
| 52 return new AudioManagerAndroid(context); | 144 return new AudioManagerAndroid(context); |
| 53 } | 145 } |
| 54 | 146 |
| 55 private AudioManagerAndroid(Context context) { | 147 private AudioManagerAndroid(Context context) { |
| 56 mContext = context; | 148 mContext = context; |
| 57 mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SE RVICE); | 149 mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SE RVICE); |
| 58 } | 150 } |
| 59 | 151 |
| 152 /** | |
| 153 * Pupulate the list of available audio devices and register receivers | |
|
Jói
2013/11/25 13:09:35
Pupulate -> Populate
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
| |
| 154 * for broadcasted intents related to wired headset and bluetooth devices. | |
| 155 * TODO(henrika): we should probably store an initial entry state here | |
| 156 * as well. | |
| 157 */ | |
| 158 @CalledByNative | |
| 159 public void init() { | |
| 160 if (mIsInitialized) | |
| 161 return; | |
| 162 | |
| 163 // Initialize audio device list with things we know is always available. | |
| 164 if (hasEarpiece()) { | |
| 165 mAudioDevices.add(AudioDevice.EARPIECE); | |
| 166 } | |
| 167 mAudioDevices.add(AudioDevice.SPEAKERPHONE); | |
| 168 | |
| 169 registerForWiredHeadsetIntentBroadcast(); | |
| 170 initBluetooth(); | |
| 171 | |
| 172 logd("init: devices=" + mAudioDevices); | |
| 173 mIsInitialized = true; | |
| 174 } | |
| 175 | |
| 176 /** | |
| 177 * Unregister all previously registered intent receivers. | |
| 178 * TODO(henrika): we should probably restore the initial entry state here | |
| 179 * as well. | |
| 180 */ | |
| 181 @CalledByNative | |
| 182 public void close() { | |
| 183 if (!mIsInitialized) | |
| 184 return; | |
| 185 | |
| 186 logd("close"); | |
| 187 unregisterForWiredHeadsetIntentBroadcast(); | |
| 188 mIsInitialized = false; | |
| 189 } | |
| 190 | |
| 191 @CalledByNative | |
| 192 public String[] getAudioOutputDeviceNames() { | |
| 193 logd("getAudioOutputDeviceNames"); | |
| 194 return toStringArray(mAudioDevices); | |
| 195 } | |
| 196 | |
| 60 @CalledByNative | 197 @CalledByNative |
| 61 public void registerHeadsetReceiver() { | 198 public void registerHeadsetReceiver() { |
| 62 if (mReceiver != null) { | 199 if (mReceiver != null) { |
| 63 return; | 200 return; |
| 64 } | 201 } |
| 65 | 202 |
| 66 mOriginalSpeakerStatus = mAudioManager.isSpeakerphoneOn(); | 203 mOriginalSpeakerStatus = mAudioManager.isSpeakerphoneOn(); |
| 67 if (!mOriginalSpeakerStatus) { | 204 if (!mOriginalSpeakerStatus) { |
| 68 mAudioManager.setSpeakerphoneOn(true); | 205 mAudioManager.setSpeakerphoneOn(true); |
| 69 } | 206 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 158 } | 295 } |
| 159 | 296 |
| 160 @CalledByNative | 297 @CalledByNative |
| 161 private int getAudioLowLatencyOutputFrameSize() { | 298 private int getAudioLowLatencyOutputFrameSize() { |
| 162 String framesPerBuffer = | 299 String framesPerBuffer = |
| 163 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PE R_BUFFER); | 300 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PE R_BUFFER); |
| 164 return (framesPerBuffer == null ? | 301 return (framesPerBuffer == null ? |
| 165 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer)); | 302 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer)); |
| 166 } | 303 } |
| 167 | 304 |
| 305 /** | |
| 306 * Returns true if the device has a headset earpice and false if not. | |
|
Jói
2013/11/25 13:09:35
I think the convention for JavaDoc comments is for
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
| |
| 307 */ | |
| 308 private boolean hasEarpiece() { | |
| 309 boolean hasFeature = mContext.getPackageManager().hasSystemFeature( | |
| 310 PackageManager.FEATURE_TELEPHONY); | |
| 311 return hasFeature; | |
| 312 } | |
| 313 | |
| 314 /** | |
| 315 * Converts a set of audio devices into array of strings. | |
| 316 */ | |
| 317 private static String[] toStringArray(Set<AudioDevice> devices) { | |
| 318 if (devices == null) { | |
| 319 return null; | |
| 320 } | |
| 321 String[] retVal = new String[devices.size()]; | |
| 322 int i = 0; | |
| 323 for (AudioDevice device : devices) { | |
| 324 retVal[i] = device.toString(); | |
| 325 i++; | |
| 326 } | |
| 327 return retVal; | |
| 328 } | |
| 329 | |
| 330 /** | |
| 331 * Registers receiver for the broadcasted intent when a Wired Headset is | |
| 332 * plugged in or unplugged. The received intent will have an extra | |
| 333 * 'state' value where 0 means unplugged, and 1 means plugged. | |
| 334 */ | |
| 335 private void registerForWiredHeadsetIntentBroadcast() { | |
| 336 logd("registerForWiredHeadsetIntentBroadcast"); | |
| 337 IntentFilter filter = new IntentFilter(); | |
| 338 filter.addAction(Intent.ACTION_HEADSET_PLUG); | |
| 339 mContext.registerReceiver(mWiredHeadsetReceiver, filter); | |
| 340 // Note: the intent we just registered for is sticky, so it'll tell us | |
| 341 // immediately what the last action was (plugged or unplugged). | |
| 342 // It will enable us to set the speakerphone correctly. | |
| 343 } | |
| 344 | |
| 345 /** | |
| 346 * Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. | |
| 347 */ | |
| 348 private void unregisterForWiredHeadsetIntentBroadcast() { | |
| 349 logd("unregisterForWiredHeadsetIntentBroadcast"); | |
| 350 mContext.unregisterReceiver(mWiredHeadsetReceiver); | |
| 351 mWiredHeadsetReceiver = null; | |
| 352 } | |
| 353 | |
| 354 | |
| 355 /** | |
| 356 * Check if Bluetooth device is connected, register Bluetooth receiver | |
| 357 * and start routing to Bluetooth if a device is connected. | |
| 358 * TODO(henrika): currently only supports the detecion part. | |
| 359 */ | |
| 360 private void initBluetooth() { | |
| 361 logd("initBluetooth"); | |
| 362 | |
| 363 // To get a BluetoothAdapter representing the local Bluetooth adapter, | |
| 364 // when running on JELLY_BEAN_MR1 (4.2) and below, call the static | |
| 365 // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and | |
| 366 // higher, retrieve it through getSystemService(String) with | |
| 367 // BLUETOOTH_SERVICE. | |
| 368 // Note: Most methods require the BLUETOOTH permission. | |
| 369 BluetoothAdapter btAdapter = null; | |
| 370 if (android.os.Build.VERSION.SDK_INT <= | |
| 371 android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
| 372 // Use static method for Android 4.2 and below to get the | |
| 373 // BluetoothAdapter. | |
| 374 btAdapter = BluetoothAdapter.getDefaultAdapter(); | |
| 375 } else { | |
| 376 // Use BluetoothManager to get the BluetoothAdapter for | |
| 377 // Android 4.3 and above. | |
| 378 BluetoothManager btManager = | |
| 379 (BluetoothManager)mContext.getSystemService( | |
| 380 Context.BLUETOOTH_SERVICE); | |
| 381 btAdapter = btManager.getAdapter(); | |
| 382 } | |
| 383 | |
| 384 if (btAdapter != null && | |
| 385 android.bluetooth.BluetoothProfile.STATE_CONNECTED == | |
| 386 btAdapter.getProfileConnectionState( | |
| 387 android.bluetooth.BluetoothProfile.HEADSET)) { | |
| 388 logd("BT device was connected at start of call"); | |
|
Jói
2013/11/25 13:09:35
The indent here seems off, shouldn't it be 4 in fr
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
| |
| 389 mAudioDevices.add(AudioDevice.BLUETOOTH_HEADSET); | |
| 390 // TODO(henrika): ensure that we set the active audio | |
| 391 // device to Bluetooth (not trivial). | |
| 392 // setAudioDevice(AudioDevice.BLUETOOTH_HEADSET); | |
| 393 } | |
| 394 } | |
| 395 | |
| 396 /** | |
| 397 * Trivial helper method for debug logging. | |
| 398 */ | |
| 399 private void logd(String msg) { | |
| 400 Log.d(TAG, msg); | |
| 401 } | |
| 168 } | 402 } |
| OLD | NEW |