diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java index 5f641bc653..04c853ef59 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java @@ -27,32 +27,107 @@ package org.appspot.apprtc; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.media.AudioManager; 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. - * TODO(henrika): add support for device enumeration, device selection etc. */ public class 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 AudioManager audioManager; private int savedAudioMode = AudioManager.MODE_INVALID; private boolean savedIsSpeakerPhoneOn = false; private boolean savedIsMicrophoneMute = false; - /** Construction */ - static AppRTCAudioManager create(Context context) { - return new AppRTCAudioManager(context); + // For now; always use the speaker phone as default device selection when + // there is a choice between SPEAKER_PHONE and EARPIECE. + // 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 audioDevices = new HashSet(); + + // 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) { - Log.d(TAG, "AppRTCAudioManager"); + /** Construction */ + 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( 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() { @@ -66,11 +141,27 @@ public class AppRTCAudioManager { savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); 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 // result in best possible "VoIP settings", like audio routing, volume // control etc. 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; } @@ -80,14 +171,111 @@ public class AppRTCAudioManager { return; } + unregisterForWiredHeadsetIntentBroadcast(); + // Restore previously stored audio states. setSpeakerphoneOn(savedIsSpeakerPhoneOn); setMicrophoneMute(savedIsMicrophoneMute); audioManager.setMode(savedAudioMode); + audioManager.abandonAudioFocus(null); + + if (proximitySensor != null) { + proximitySensor.stop(); + proximitySensor = null; + } 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 getAudioDevices() { + return Collections.unmodifiableSet(new HashSet(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. */ private void setSpeakerphoneOn(boolean on) { boolean wasOn = audioManager.isSpeakerphoneOn(); @@ -105,4 +293,74 @@ public class AppRTCAudioManager { } 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(); + } + } } diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java index 72adf89118..bc8c629382 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java @@ -203,7 +203,14 @@ public class AppRTCDemoActivity extends Activity // Create and audio manager that will take care of audio routing, // 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(); 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. private void disconnect() { if (appRtcClient != null) { @@ -595,5 +607,4 @@ public class AppRTCDemoActivity extends Activity disconnectWithErrorMessage(description); } } - } diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCProximitySensor.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCProximitySensor.java new file mode 100644 index 0000000000..76f5d8f2bd --- /dev/null +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCProximitySensor.java @@ -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"); + } + } +} diff --git a/talk/examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java b/talk/examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java new file mode 100644 index 0000000000..51896ba226 --- /dev/null +++ b/talk/examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java @@ -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); + } +} diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp index 63aa5f1117..8f806f43bf 100755 --- a/talk/libjingle_examples.gyp +++ b/talk/libjingle_examples.gyp @@ -327,12 +327,14 @@ 'examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java', 'examples/android/src/org/appspot/apprtc/AppRTCClient.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/PeerConnectionClient.java', 'examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java', 'examples/android/src/org/appspot/apprtc/SettingsActivity.java', 'examples/android/src/org/appspot/apprtc/SettingsFragment.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/WebSocketRTCClient.java', ],