Add support for audio device selection in AppRTCDemo.
Summary: - Creates a list of available (possible to select) audio devices. - Automatically selects (routes audio) the "best/default" audio device. - If possible, starts a proximity sensor that will switch between headset earpiece and speaker phone based on how close the a person's ear the mobile device is held. TBR=glaznev BUG=4103,4109 Review URL: https://webrtc-codereview.appspot.com/31239004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7978 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -27,32 +27,107 @@
|
|||||||
|
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppRTCAudioManager manages all audio related parts of the AppRTC demo.
|
* AppRTCAudioManager manages all audio related parts of the AppRTC demo.
|
||||||
* TODO(henrika): add support for device enumeration, device selection etc.
|
|
||||||
*/
|
*/
|
||||||
public class AppRTCAudioManager {
|
public class AppRTCAudioManager {
|
||||||
private static final String TAG = "AppRTCAudioManager";
|
private static final String TAG = "AppRTCAudioManager";
|
||||||
|
|
||||||
|
// Names of possible audio devices that we currently support.
|
||||||
|
// TODO(henrika): add support for BLUETOOTH as well.
|
||||||
|
public enum AudioDevice {
|
||||||
|
SPEAKER_PHONE,
|
||||||
|
WIRED_HEADSET,
|
||||||
|
EARPIECE,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context apprtcContext;
|
||||||
|
private final Runnable onStateChangeListener;
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
private AudioManager audioManager;
|
private AudioManager audioManager;
|
||||||
private int savedAudioMode = AudioManager.MODE_INVALID;
|
private int savedAudioMode = AudioManager.MODE_INVALID;
|
||||||
private boolean savedIsSpeakerPhoneOn = false;
|
private boolean savedIsSpeakerPhoneOn = false;
|
||||||
private boolean savedIsMicrophoneMute = false;
|
private boolean savedIsMicrophoneMute = false;
|
||||||
|
|
||||||
/** Construction */
|
// For now; always use the speaker phone as default device selection when
|
||||||
static AppRTCAudioManager create(Context context) {
|
// there is a choice between SPEAKER_PHONE and EARPIECE.
|
||||||
return new AppRTCAudioManager(context);
|
// TODO(henrika): it is possible that EARPIECE should be preferred in some
|
||||||
|
// cases. If so, we should set this value at construction instead.
|
||||||
|
private final AudioDevice defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
|
||||||
|
|
||||||
|
// Proximity sensor object. It measures the proximity of an object in cm
|
||||||
|
// relative to the view screen of a device and can therefore be used to
|
||||||
|
// assist device switching (close to ear <=> use headset earpiece if
|
||||||
|
// available, far from ear <=> use speaker phone).
|
||||||
|
private AppRTCProximitySensor proximitySensor = null;
|
||||||
|
|
||||||
|
// Contains the currently selected audio device.
|
||||||
|
private AudioDevice selectedAudioDevice;
|
||||||
|
|
||||||
|
// Contains a list of available audio devices. A Set collection is used to
|
||||||
|
// avoid duplicate elements.
|
||||||
|
private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
|
||||||
|
|
||||||
|
// Broadcast receiver for wired headset intent broadcasts.
|
||||||
|
private BroadcastReceiver wiredHeadsetReceiver;
|
||||||
|
|
||||||
|
// This method is called when the proximity sensor reports a state change,
|
||||||
|
// e.g. from "NEAR to FAR" or from "FAR to NEAR".
|
||||||
|
private void onProximitySensorChangedState() {
|
||||||
|
// The proximity sensor should only be activated when there are exactly two
|
||||||
|
// available audio devices.
|
||||||
|
if (audioDevices.size() == 2 &&
|
||||||
|
audioDevices.contains(AppRTCAudioManager.AudioDevice.EARPIECE) &&
|
||||||
|
audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) {
|
||||||
|
if (proximitySensor.sensorReportsNearState()) {
|
||||||
|
// Sensor reports that a "handset is being held up to a person's ear",
|
||||||
|
// or "something is covering the light sensor".
|
||||||
|
setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
|
||||||
|
} else {
|
||||||
|
// Sensor reports that a "handset is removed from a person's ear", or
|
||||||
|
// "the light sensor is no longer covered".
|
||||||
|
setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppRTCAudioManager(Context context) {
|
/** Construction */
|
||||||
Log.d(TAG, "AppRTCAudioManager");
|
static AppRTCAudioManager create(Context context,
|
||||||
|
Runnable deviceStateChangeListener) {
|
||||||
|
return new AppRTCAudioManager(context, deviceStateChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppRTCAudioManager(Context context,
|
||||||
|
Runnable deviceStateChangeListener) {
|
||||||
|
apprtcContext = context;
|
||||||
|
onStateChangeListener = deviceStateChangeListener;
|
||||||
audioManager = ((AudioManager) context.getSystemService(
|
audioManager = ((AudioManager) context.getSystemService(
|
||||||
Context.AUDIO_SERVICE));
|
Context.AUDIO_SERVICE));
|
||||||
|
|
||||||
|
// Create and initialize the proximity sensor.
|
||||||
|
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
||||||
|
// Note that, the sensor will not be active until start() has been called.
|
||||||
|
proximitySensor = AppRTCProximitySensor.create(context, new Runnable() {
|
||||||
|
// This method will be called each time a state change is detected.
|
||||||
|
// Example: user holds his hand over the device (closer than ~5 cm),
|
||||||
|
// or removes his hand from the device.
|
||||||
|
public void run() {
|
||||||
|
onProximitySensorChangedState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AppRTCUtils.logDeviceInfo(TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -66,11 +141,27 @@ public class AppRTCAudioManager {
|
|||||||
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
||||||
savedIsMicrophoneMute = audioManager.isMicrophoneMute();
|
savedIsMicrophoneMute = audioManager.isMicrophoneMute();
|
||||||
|
|
||||||
|
// Request audio focus before making any device switch.
|
||||||
|
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||||
|
|
||||||
// The AppRTC demo shall always run in COMMUNICATION mode since it will
|
// The AppRTC demo shall always run in COMMUNICATION mode since it will
|
||||||
// result in best possible "VoIP settings", like audio routing, volume
|
// result in best possible "VoIP settings", like audio routing, volume
|
||||||
// control etc.
|
// control etc.
|
||||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||||
|
|
||||||
|
// Always disable microphone mute during a WebRTC call.
|
||||||
|
setMicrophoneMute(false);
|
||||||
|
|
||||||
|
// Do initial selection of audio device. This setting can later be changed
|
||||||
|
// either by adding/removing a wired headset or by covering/uncovering the
|
||||||
|
// proximity sensor.
|
||||||
|
updateAudioDeviceState(hasWiredHeadset());
|
||||||
|
|
||||||
|
// Register receiver for broadcast intents related to adding/removing a
|
||||||
|
// wired headset (Intent.ACTION_HEADSET_PLUG).
|
||||||
|
registerForWiredHeadsetIntentBroadcast();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +171,111 @@ public class AppRTCAudioManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unregisterForWiredHeadsetIntentBroadcast();
|
||||||
|
|
||||||
// Restore previously stored audio states.
|
// Restore previously stored audio states.
|
||||||
setSpeakerphoneOn(savedIsSpeakerPhoneOn);
|
setSpeakerphoneOn(savedIsSpeakerPhoneOn);
|
||||||
setMicrophoneMute(savedIsMicrophoneMute);
|
setMicrophoneMute(savedIsMicrophoneMute);
|
||||||
audioManager.setMode(savedAudioMode);
|
audioManager.setMode(savedAudioMode);
|
||||||
|
audioManager.abandonAudioFocus(null);
|
||||||
|
|
||||||
|
if (proximitySensor != null) {
|
||||||
|
proximitySensor.stop();
|
||||||
|
proximitySensor = null;
|
||||||
|
}
|
||||||
|
|
||||||
initialized = false;
|
initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Changes selection of the currently active audio device. */
|
||||||
|
public void setAudioDevice(AudioDevice device) {
|
||||||
|
Log.d(TAG, "setAudioDevice(device=" + device + ")");
|
||||||
|
AppRTCUtils.assertIsTrue(audioDevices.contains(device));
|
||||||
|
|
||||||
|
switch (device) {
|
||||||
|
case SPEAKER_PHONE:
|
||||||
|
setSpeakerphoneOn(true);
|
||||||
|
selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
||||||
|
break;
|
||||||
|
case EARPIECE:
|
||||||
|
setSpeakerphoneOn(false);
|
||||||
|
selectedAudioDevice = AudioDevice.EARPIECE;
|
||||||
|
break;
|
||||||
|
case WIRED_HEADSET:
|
||||||
|
setSpeakerphoneOn(false);
|
||||||
|
selectedAudioDevice = AudioDevice.WIRED_HEADSET;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Invalid audio device selection");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
onAudioManagerChangedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns current set of available/selectable audio devices. */
|
||||||
|
public Set<AudioDevice> getAudioDevices() {
|
||||||
|
return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the currently selected audio device. */
|
||||||
|
public AudioDevice getSelectedAudioDevice() {
|
||||||
|
return selectedAudioDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers receiver for the broadcasted intent when a wired headset is
|
||||||
|
* plugged in or unplugged. The received intent will have an extra
|
||||||
|
* 'state' value where 0 means unplugged, and 1 means plugged.
|
||||||
|
*/
|
||||||
|
private void registerForWiredHeadsetIntentBroadcast() {
|
||||||
|
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||||
|
|
||||||
|
/** Receiver which handles changes in wired headset availability. */
|
||||||
|
wiredHeadsetReceiver = new BroadcastReceiver() {
|
||||||
|
private static final int STATE_UNPLUGGED = 0;
|
||||||
|
private static final int STATE_PLUGGED = 1;
|
||||||
|
private static final int HAS_NO_MIC = 0;
|
||||||
|
private static final int HAS_MIC = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int state = intent.getIntExtra("state", STATE_UNPLUGGED);
|
||||||
|
int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
|
||||||
|
String name = intent.getStringExtra("name");
|
||||||
|
Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo()
|
||||||
|
+ ": "
|
||||||
|
+ "a=" + intent.getAction()
|
||||||
|
+ ", s=" + (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
|
||||||
|
+ ", m=" + (microphone == HAS_MIC ? "mic" : "no mic")
|
||||||
|
+ ", n=" + name
|
||||||
|
+ ", sb=" + isInitialStickyBroadcast());
|
||||||
|
|
||||||
|
boolean hasWiredHeadset = (state == STATE_PLUGGED) ? true : false;
|
||||||
|
switch (state) {
|
||||||
|
case STATE_UNPLUGGED:
|
||||||
|
updateAudioDeviceState(hasWiredHeadset);
|
||||||
|
break;
|
||||||
|
case STATE_PLUGGED:
|
||||||
|
if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) {
|
||||||
|
updateAudioDeviceState(hasWiredHeadset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Invalid state");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apprtcContext.registerReceiver(wiredHeadsetReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
|
||||||
|
private void unregisterForWiredHeadsetIntentBroadcast() {
|
||||||
|
apprtcContext.unregisterReceiver(wiredHeadsetReceiver);
|
||||||
|
wiredHeadsetReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets the speaker phone mode. */
|
/** Sets the speaker phone mode. */
|
||||||
private void setSpeakerphoneOn(boolean on) {
|
private void setSpeakerphoneOn(boolean on) {
|
||||||
boolean wasOn = audioManager.isSpeakerphoneOn();
|
boolean wasOn = audioManager.isSpeakerphoneOn();
|
||||||
@@ -105,4 +293,74 @@ public class AppRTCAudioManager {
|
|||||||
}
|
}
|
||||||
audioManager.setMicrophoneMute(on);
|
audioManager.setMicrophoneMute(on);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the current earpiece state. */
|
||||||
|
private boolean hasEarpiece() {
|
||||||
|
return apprtcContext.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 audioManager.isWiredHeadsetOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update list of possible audio devices and make new device selection. */
|
||||||
|
private void updateAudioDeviceState(boolean hasWiredHeadset) {
|
||||||
|
// Update the list of available audio devices.
|
||||||
|
audioDevices.clear();
|
||||||
|
if (hasWiredHeadset) {
|
||||||
|
// If a wired headset is connected, then it is the only possible option.
|
||||||
|
audioDevices.add(AudioDevice.WIRED_HEADSET);
|
||||||
|
} else {
|
||||||
|
// No wired headset, hence the audio-device list can contain speaker
|
||||||
|
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
|
||||||
|
audioDevices.add(AudioDevice.SPEAKER_PHONE);
|
||||||
|
if (hasEarpiece()) {
|
||||||
|
audioDevices.add(AudioDevice.EARPIECE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "audioDevices: " + audioDevices);
|
||||||
|
|
||||||
|
// Switch to correct audio device given the list of available audio devices.
|
||||||
|
if (hasWiredHeadset) {
|
||||||
|
setAudioDevice(AudioDevice.WIRED_HEADSET);
|
||||||
|
} else {
|
||||||
|
setAudioDevice(defaultAudioDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called each time a new audio device has been added or removed. */
|
||||||
|
private void onAudioManagerChangedState() {
|
||||||
|
Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices
|
||||||
|
+ ", selected=" + selectedAudioDevice);
|
||||||
|
|
||||||
|
// Enable the proximity sensor if there are two available audio devices
|
||||||
|
// in the list. Given the current implementation, we know that the choice
|
||||||
|
// will then be between EARPIECE and SPEAKER_PHONE.
|
||||||
|
if (audioDevices.size() == 2) {
|
||||||
|
AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) &&
|
||||||
|
audioDevices.contains(AudioDevice.SPEAKER_PHONE));
|
||||||
|
// Start the proximity sensor.
|
||||||
|
proximitySensor.start();
|
||||||
|
} else if (audioDevices.size() == 1) {
|
||||||
|
// Stop the proximity sensor since it is no longer needed.
|
||||||
|
proximitySensor.stop();
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Invalid device list");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onStateChangeListener != null) {
|
||||||
|
// Run callback to notify a listening client. The client can then
|
||||||
|
// use public getters to query the new state.
|
||||||
|
onStateChangeListener.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,14 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
|
|
||||||
// Create and audio manager that will take care of audio routing,
|
// Create and audio manager that will take care of audio routing,
|
||||||
// audio modes, audio device enumeration etc.
|
// audio modes, audio device enumeration etc.
|
||||||
audioManager = AppRTCAudioManager.create(this);
|
audioManager = AppRTCAudioManager.create(this, new Runnable() {
|
||||||
|
// This method will be called each time the audio state (number and
|
||||||
|
// type of devices) has been changed.
|
||||||
|
public void run() {
|
||||||
|
onAudioManagerChangedState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
Uri url = intent.getData();
|
Uri url = intent.getData();
|
||||||
@@ -294,6 +301,11 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onAudioManagerChangedState() {
|
||||||
|
// TODO(henrika): disable video if AppRTCAudioManager.AudioDevice.EARPIECE
|
||||||
|
// is active.
|
||||||
|
}
|
||||||
|
|
||||||
// Disconnect from remote resources, dispose of local resources, and exit.
|
// Disconnect from remote resources, dispose of local resources, and exit.
|
||||||
private void disconnect() {
|
private void disconnect() {
|
||||||
if (appRtcClient != null) {
|
if (appRtcClient != null) {
|
||||||
@@ -595,5 +607,4 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
disconnectWithErrorMessage(description);
|
disconnectWithErrorMessage(description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* libjingle
|
||||||
|
* Copyright 2014, Google Inc.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. The name of the author may not be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.util.List;
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils;
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils.NonThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppRTCProximitySensor manages functions related to the proximity sensor in
|
||||||
|
* the AppRTC demo.
|
||||||
|
* On most device, the proximity sensor is implemented as a boolean-sensor.
|
||||||
|
* It returns just two values "NEAR" or "FAR". Thresholding is done on the LUX
|
||||||
|
* value i.e. the LUX value of the light sensor is compared with a threshold.
|
||||||
|
* A LUX-value more than the threshold means the proximity sensor returns "FAR".
|
||||||
|
* Anything less than the threshold value and the sensor returns "NEAR".
|
||||||
|
*/
|
||||||
|
public class AppRTCProximitySensor implements SensorEventListener {
|
||||||
|
private static final String TAG = "AppRTCProximitySensor";
|
||||||
|
|
||||||
|
// This class should be created, started and stopped on one thread
|
||||||
|
// (e.g. the main thread). We use |nonThreadSafe| to ensure that this is
|
||||||
|
// the case. Only active when |DEBUG| is set to true.
|
||||||
|
private final NonThreadSafe nonThreadSafe = new AppRTCUtils.NonThreadSafe();
|
||||||
|
|
||||||
|
private final Context apprtcContext;
|
||||||
|
private final Runnable onSensorStateListener;
|
||||||
|
private final SensorManager sensorManager;
|
||||||
|
private Sensor proximitySensor = null;
|
||||||
|
private boolean initialized = false;
|
||||||
|
private boolean lastStateReportIsNear = false;
|
||||||
|
|
||||||
|
/** Construction */
|
||||||
|
static AppRTCProximitySensor create(Context context,
|
||||||
|
Runnable sensorStateListener) {
|
||||||
|
return new AppRTCProximitySensor(context, sensorStateListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppRTCProximitySensor(Context context, Runnable sensorStateListener) {
|
||||||
|
Log.d(TAG, "AppRTCProximitySensor" + AppRTCUtils.getThreadInfo());
|
||||||
|
apprtcContext = context;
|
||||||
|
onSensorStateListener = sensorStateListener;
|
||||||
|
sensorManager = ((SensorManager) context.getSystemService(
|
||||||
|
Context.SENSOR_SERVICE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the proximity sensor. Also do initializtion if called for the
|
||||||
|
* first time.
|
||||||
|
*/
|
||||||
|
public boolean start() {
|
||||||
|
checkIfCalledOnValidThread();
|
||||||
|
Log.d(TAG, "start" + AppRTCUtils.getThreadInfo());
|
||||||
|
if (!initDefaultSensor()) {
|
||||||
|
// Proximity sensor is not supported on this device.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sensorManager.registerListener(
|
||||||
|
this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deactivate the proximity sensor. */
|
||||||
|
public void stop() {
|
||||||
|
checkIfCalledOnValidThread();
|
||||||
|
Log.d(TAG, "stop" + AppRTCUtils.getThreadInfo());
|
||||||
|
if (proximitySensor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sensorManager.unregisterListener(this, proximitySensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Getter for last reported state. Set to true if "near" is reported. */
|
||||||
|
public boolean sensorReportsNearState() {
|
||||||
|
checkIfCalledOnValidThread();
|
||||||
|
return lastStateReportIsNear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
checkIfCalledOnValidThread();
|
||||||
|
AppRTCUtils.assertIsTrue(sensor.getType() == Sensor.TYPE_PROXIMITY);
|
||||||
|
if (accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
|
||||||
|
Log.e(TAG, "The values returned by this sensor cannot be trusted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onSensorChanged(SensorEvent event) {
|
||||||
|
checkIfCalledOnValidThread();
|
||||||
|
AppRTCUtils.assertIsTrue(event.sensor.getType() == Sensor.TYPE_PROXIMITY);
|
||||||
|
// As a best practice; do as little as possible within this method and
|
||||||
|
// avoid blocking.
|
||||||
|
float distanceInCentimeters = event.values[0];
|
||||||
|
if (distanceInCentimeters < proximitySensor.getMaximumRange()) {
|
||||||
|
Log.d(TAG, "Proximity sensor => NEAR state");
|
||||||
|
lastStateReportIsNear = true;
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Proximity sensor => FAR state");
|
||||||
|
lastStateReportIsNear = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report about new state to listening client. Client can then call
|
||||||
|
// sensorReportsNearState() to query the current state (NEAR or FAR).
|
||||||
|
if (onSensorStateListener != null) {
|
||||||
|
onSensorStateListener.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "onSensorChanged" + AppRTCUtils.getThreadInfo() + ": "
|
||||||
|
+ "accuracy=" + event.accuracy
|
||||||
|
+ ", timestamp=" + event.timestamp + ", distance=" + event.values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default proximity sensor if it exists. Tablet devices (e.g. Nexus 7)
|
||||||
|
* does not support this type of sensor and false will be retured in such
|
||||||
|
* cases.
|
||||||
|
*/
|
||||||
|
private boolean initDefaultSensor() {
|
||||||
|
if (proximitySensor != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
|
||||||
|
if (proximitySensor == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logProximitySensorInfo();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method for logging information about the proximity sensor. */
|
||||||
|
private void logProximitySensorInfo() {
|
||||||
|
if (proximitySensor == null)
|
||||||
|
return;
|
||||||
|
Log.d(TAG, "Proximity sensor: " + "name=" + proximitySensor.getName()
|
||||||
|
+ ", vendor: " + proximitySensor.getVendor()
|
||||||
|
+ ", type: " + proximitySensor.getStringType()
|
||||||
|
+ ", reporting mode: " + proximitySensor.getReportingMode()
|
||||||
|
+ ", power: " + proximitySensor.getPower()
|
||||||
|
+ ", min delay: " + proximitySensor.getMinDelay()
|
||||||
|
+ ", max delay: " + proximitySensor.getMaxDelay()
|
||||||
|
+ ", resolution: " + proximitySensor.getResolution()
|
||||||
|
+ ", max range: " + proximitySensor.getMaximumRange()
|
||||||
|
+ ", isWakeUpSensor: " + proximitySensor.isWakeUpSensor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for debugging purposes. Ensures that method is
|
||||||
|
* called on same thread as this object was created on.
|
||||||
|
*/
|
||||||
|
private void checkIfCalledOnValidThread() {
|
||||||
|
if (!nonThreadSafe.calledOnValidThread()) {
|
||||||
|
throw new IllegalStateException("Method is not called on valid thread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* libjingle
|
||||||
|
* Copyright 2014, Google Inc.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. The name of the author may not be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.appspot.apprtc.util;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.lang.Thread;
|
||||||
|
|
||||||
|
public final class AppRTCUtils {
|
||||||
|
|
||||||
|
private AppRTCUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NonThreadSafe is a helper class used to help verify that methods of a
|
||||||
|
* class are called from the same thread.
|
||||||
|
*/
|
||||||
|
public static class NonThreadSafe {
|
||||||
|
private final Long threadId;
|
||||||
|
|
||||||
|
public NonThreadSafe() {
|
||||||
|
// Store thread ID of the creating thread.
|
||||||
|
threadId = Thread.currentThread().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks if the method is called on the valid/creating thread. */
|
||||||
|
public boolean calledOnValidThread() {
|
||||||
|
return threadId.equals(Thread.currentThread().getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method which throws an exception when an assertion has failed. */
|
||||||
|
public static void assertIsTrue(boolean condition) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError("Expected condition to be true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method for building a string of thread information.*/
|
||||||
|
public static String getThreadInfo() {
|
||||||
|
return "@[name=" + Thread.currentThread().getName()
|
||||||
|
+ ", id=" + Thread.currentThread().getId() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Information about the current build, taken from system properties. */
|
||||||
|
public static void logDeviceInfo(String tag) {
|
||||||
|
Log.d(tag, "Android SDK: " + Build.VERSION.SDK_INT + ", "
|
||||||
|
+ "Release: " + Build.VERSION.RELEASE + ", "
|
||||||
|
+ "Brand: " + Build.BRAND + ", "
|
||||||
|
+ "Device: " + Build.DEVICE + ", "
|
||||||
|
+ "Id: " + Build.ID + ", "
|
||||||
|
+ "Hardware: " + Build.HARDWARE + ", "
|
||||||
|
+ "Manufacturer: " + Build.MANUFACTURER + ", "
|
||||||
|
+ "Model: " + Build.MODEL + ", "
|
||||||
|
+ "Product: " + Build.PRODUCT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -327,12 +327,14 @@
|
|||||||
'examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java',
|
'examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java',
|
||||||
'examples/android/src/org/appspot/apprtc/AppRTCClient.java',
|
'examples/android/src/org/appspot/apprtc/AppRTCClient.java',
|
||||||
'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java',
|
'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java',
|
||||||
|
'examples/android/src/org/appspot/apprtc/AppRTCProximitySensor.java',
|
||||||
'examples/android/src/org/appspot/apprtc/ConnectActivity.java',
|
'examples/android/src/org/appspot/apprtc/ConnectActivity.java',
|
||||||
'examples/android/src/org/appspot/apprtc/PeerConnectionClient.java',
|
'examples/android/src/org/appspot/apprtc/PeerConnectionClient.java',
|
||||||
'examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java',
|
'examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java',
|
||||||
'examples/android/src/org/appspot/apprtc/SettingsActivity.java',
|
'examples/android/src/org/appspot/apprtc/SettingsActivity.java',
|
||||||
'examples/android/src/org/appspot/apprtc/SettingsFragment.java',
|
'examples/android/src/org/appspot/apprtc/SettingsFragment.java',
|
||||||
'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java',
|
'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java',
|
||||||
|
'examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java',
|
||||||
'examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java',
|
'examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java',
|
||||||
'examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java',
|
'examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user