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 |