Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(210)

Side by Side Diff: talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java

Issue 1235563006: Move talk/examples/* to webrtc/examples. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: 201508051337 Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * libjingle
3 * Copyright 2014 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 package org.appspot.apprtc;
29
30 import org.appspot.apprtc.util.AppRTCUtils;
31
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.PackageManager;
37 import android.media.AudioManager;
38 import android.util.Log;
39
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.Set;
43
44 /**
45 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
46 */
47 public class AppRTCAudioManager {
48 private static final String TAG = "AppRTCAudioManager";
49
50 /**
51 * AudioDevice is the names of possible audio devices that we currently
52 * support.
53 */
54 // TODO(henrika): add support for BLUETOOTH as well.
55 public enum AudioDevice {
56 SPEAKER_PHONE,
57 WIRED_HEADSET,
58 EARPIECE,
59 }
60
61 private final Context apprtcContext;
62 private final Runnable onStateChangeListener;
63 private boolean initialized = false;
64 private AudioManager audioManager;
65 private int savedAudioMode = AudioManager.MODE_INVALID;
66 private boolean savedIsSpeakerPhoneOn = false;
67 private boolean savedIsMicrophoneMute = false;
68
69 // For now; always use the speaker phone as default device selection when
70 // there is a choice between SPEAKER_PHONE and EARPIECE.
71 // TODO(henrika): it is possible that EARPIECE should be preferred in some
72 // cases. If so, we should set this value at construction instead.
73 private final AudioDevice defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
74
75 // Proximity sensor object. It measures the proximity of an object in cm
76 // relative to the view screen of a device and can therefore be used to
77 // assist device switching (close to ear <=> use headset earpiece if
78 // available, far from ear <=> use speaker phone).
79 private AppRTCProximitySensor proximitySensor = null;
80
81 // Contains the currently selected audio device.
82 private AudioDevice selectedAudioDevice;
83
84 // Contains a list of available audio devices. A Set collection is used to
85 // avoid duplicate elements.
86 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
87
88 // Broadcast receiver for wired headset intent broadcasts.
89 private BroadcastReceiver wiredHeadsetReceiver;
90
91 // This method is called when the proximity sensor reports a state change,
92 // e.g. from "NEAR to FAR" or from "FAR to NEAR".
93 private void onProximitySensorChangedState() {
94 // The proximity sensor should only be activated when there are exactly two
95 // available audio devices.
96 if (audioDevices.size() == 2
97 && audioDevices.contains(AppRTCAudioManager.AudioDevice.EARPIECE)
98 && audioDevices.contains(
99 AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) {
100 if (proximitySensor.sensorReportsNearState()) {
101 // Sensor reports that a "handset is being held up to a person's ear",
102 // or "something is covering the light sensor".
103 setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
104 } else {
105 // Sensor reports that a "handset is removed from a person's ear", or
106 // "the light sensor is no longer covered".
107 setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
108 }
109 }
110 }
111
112 /** Construction */
113 static AppRTCAudioManager create(Context context,
114 Runnable deviceStateChangeListener) {
115 return new AppRTCAudioManager(context, deviceStateChangeListener);
116 }
117
118 private AppRTCAudioManager(Context context,
119 Runnable deviceStateChangeListener) {
120 apprtcContext = context;
121 onStateChangeListener = deviceStateChangeListener;
122 audioManager = ((AudioManager) context.getSystemService(
123 Context.AUDIO_SERVICE));
124
125 // Create and initialize the proximity sensor.
126 // Tablet devices (e.g. Nexus 7) does not support proximity sensors.
127 // Note that, the sensor will not be active until start() has been called.
128 proximitySensor = AppRTCProximitySensor.create(context, new Runnable() {
129 // This method will be called each time a state change is detected.
130 // Example: user holds his hand over the device (closer than ~5 cm),
131 // or removes his hand from the device.
132 public void run() {
133 onProximitySensorChangedState();
134 }
135 });
136 AppRTCUtils.logDeviceInfo(TAG);
137 }
138
139 public void init() {
140 Log.d(TAG, "init");
141 if (initialized) {
142 return;
143 }
144
145 // Store current audio state so we can restore it when close() is called.
146 savedAudioMode = audioManager.getMode();
147 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
148 savedIsMicrophoneMute = audioManager.isMicrophoneMute();
149
150 // Request audio focus before making any device switch.
151 audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
152 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
153
154 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
155 // required to be in this mode when playout and/or recording starts for
156 // best possible VoIP performance.
157 // TODO(henrika): we migh want to start with RINGTONE mode here instead.
158 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
159
160 // Always disable microphone mute during a WebRTC call.
161 setMicrophoneMute(false);
162
163 // Do initial selection of audio device. This setting can later be changed
164 // either by adding/removing a wired headset or by covering/uncovering the
165 // proximity sensor.
166 updateAudioDeviceState(hasWiredHeadset());
167
168 // Register receiver for broadcast intents related to adding/removing a
169 // wired headset (Intent.ACTION_HEADSET_PLUG).
170 registerForWiredHeadsetIntentBroadcast();
171
172 initialized = true;
173 }
174
175 public void close() {
176 Log.d(TAG, "close");
177 if (!initialized) {
178 return;
179 }
180
181 unregisterForWiredHeadsetIntentBroadcast();
182
183 // Restore previously stored audio states.
184 setSpeakerphoneOn(savedIsSpeakerPhoneOn);
185 setMicrophoneMute(savedIsMicrophoneMute);
186 audioManager.setMode(savedAudioMode);
187 audioManager.abandonAudioFocus(null);
188
189 if (proximitySensor != null) {
190 proximitySensor.stop();
191 proximitySensor = null;
192 }
193
194 initialized = false;
195 }
196
197 /** Changes selection of the currently active audio device. */
198 public void setAudioDevice(AudioDevice device) {
199 Log.d(TAG, "setAudioDevice(device=" + device + ")");
200 AppRTCUtils.assertIsTrue(audioDevices.contains(device));
201
202 switch (device) {
203 case SPEAKER_PHONE:
204 setSpeakerphoneOn(true);
205 selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
206 break;
207 case EARPIECE:
208 setSpeakerphoneOn(false);
209 selectedAudioDevice = AudioDevice.EARPIECE;
210 break;
211 case WIRED_HEADSET:
212 setSpeakerphoneOn(false);
213 selectedAudioDevice = AudioDevice.WIRED_HEADSET;
214 break;
215 default:
216 Log.e(TAG, "Invalid audio device selection");
217 break;
218 }
219 onAudioManagerChangedState();
220 }
221
222 /** Returns current set of available/selectable audio devices. */
223 public Set<AudioDevice> getAudioDevices() {
224 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
225 }
226
227 /** Returns the currently selected audio device. */
228 public AudioDevice getSelectedAudioDevice() {
229 return selectedAudioDevice;
230 }
231
232 /**
233 * Registers receiver for the broadcasted intent when a wired headset is
234 * plugged in or unplugged. The received intent will have an extra
235 * 'state' value where 0 means unplugged, and 1 means plugged.
236 */
237 private void registerForWiredHeadsetIntentBroadcast() {
238 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
239
240 /** Receiver which handles changes in wired headset availability. */
241 wiredHeadsetReceiver = new BroadcastReceiver() {
242 private static final int STATE_UNPLUGGED = 0;
243 private static final int STATE_PLUGGED = 1;
244 private static final int HAS_NO_MIC = 0;
245 private static final int HAS_MIC = 1;
246
247 @Override
248 public void onReceive(Context context, Intent intent) {
249 int state = intent.getIntExtra("state", STATE_UNPLUGGED);
250 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
251 String name = intent.getStringExtra("name");
252 Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo()
253 + ": "
254 + "a=" + intent.getAction()
255 + ", s=" + (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
256 + ", m=" + (microphone == HAS_MIC ? "mic" : "no mic")
257 + ", n=" + name
258 + ", sb=" + isInitialStickyBroadcast());
259
260 boolean hasWiredHeadset = (state == STATE_PLUGGED) ? true : false;
261 switch (state) {
262 case STATE_UNPLUGGED:
263 updateAudioDeviceState(hasWiredHeadset);
264 break;
265 case STATE_PLUGGED:
266 if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) {
267 updateAudioDeviceState(hasWiredHeadset);
268 }
269 break;
270 default:
271 Log.e(TAG, "Invalid state");
272 break;
273 }
274 }
275 };
276
277 apprtcContext.registerReceiver(wiredHeadsetReceiver, filter);
278 }
279
280 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
281 private void unregisterForWiredHeadsetIntentBroadcast() {
282 apprtcContext.unregisterReceiver(wiredHeadsetReceiver);
283 wiredHeadsetReceiver = null;
284 }
285
286 /** Sets the speaker phone mode. */
287 private void setSpeakerphoneOn(boolean on) {
288 boolean wasOn = audioManager.isSpeakerphoneOn();
289 if (wasOn == on) {
290 return;
291 }
292 audioManager.setSpeakerphoneOn(on);
293 }
294
295 /** Sets the microphone mute state. */
296 private void setMicrophoneMute(boolean on) {
297 boolean wasMuted = audioManager.isMicrophoneMute();
298 if (wasMuted == on) {
299 return;
300 }
301 audioManager.setMicrophoneMute(on);
302 }
303
304 /** Gets the current earpiece state. */
305 private boolean hasEarpiece() {
306 return apprtcContext.getPackageManager().hasSystemFeature(
307 PackageManager.FEATURE_TELEPHONY);
308 }
309
310 /**
311 * Checks whether a wired headset is connected or not.
312 * This is not a valid indication that audio playback is actually over
313 * the wired headset as audio routing depends on other conditions. We
314 * only use it as an early indicator (during initialization) of an attached
315 * wired headset.
316 */
317 @Deprecated
318 private boolean hasWiredHeadset() {
319 return audioManager.isWiredHeadsetOn();
320 }
321
322 /** Update list of possible audio devices and make new device selection. */
323 private void updateAudioDeviceState(boolean hasWiredHeadset) {
324 // Update the list of available audio devices.
325 audioDevices.clear();
326 if (hasWiredHeadset) {
327 // If a wired headset is connected, then it is the only possible option.
328 audioDevices.add(AudioDevice.WIRED_HEADSET);
329 } else {
330 // No wired headset, hence the audio-device list can contain speaker
331 // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
332 audioDevices.add(AudioDevice.SPEAKER_PHONE);
333 if (hasEarpiece()) {
334 audioDevices.add(AudioDevice.EARPIECE);
335 }
336 }
337 Log.d(TAG, "audioDevices: " + audioDevices);
338
339 // Switch to correct audio device given the list of available audio devices.
340 if (hasWiredHeadset) {
341 setAudioDevice(AudioDevice.WIRED_HEADSET);
342 } else {
343 setAudioDevice(defaultAudioDevice);
344 }
345 }
346
347 /** Called each time a new audio device has been added or removed. */
348 private void onAudioManagerChangedState() {
349 Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices
350 + ", selected=" + selectedAudioDevice);
351
352 // Enable the proximity sensor if there are two available audio devices
353 // in the list. Given the current implementation, we know that the choice
354 // will then be between EARPIECE and SPEAKER_PHONE.
355 if (audioDevices.size() == 2) {
356 AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE)
357 && audioDevices.contains(AudioDevice.SPEAKER_PHONE));
358 // Start the proximity sensor.
359 proximitySensor.start();
360 } else if (audioDevices.size() == 1) {
361 // Stop the proximity sensor since it is no longer needed.
362 proximitySensor.stop();
363 } else {
364 Log.e(TAG, "Invalid device list");
365 }
366
367 if (onStateChangeListener != null) {
368 // Run callback to notify a listening client. The client can then
369 // use public getters to query the new state.
370 onStateChangeListener.run();
371 }
372 }
373 }
OLDNEW
« no previous file with comments | « talk/examples/android/res/xml/preferences.xml ('k') | talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698