run some AppRTCAudioManager actions on main thread

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java               |  77 
src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java           |   2 
src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java            | 171 
src/main/java/eu/siacs/conversations/services/CallIntegration.java                  |  23 
src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java |   4 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java           |   3 
src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java                 |   4 
7 files changed, 30 insertions(+), 254 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java 🔗

@@ -23,6 +23,7 @@ import android.os.Build;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
 
 import org.webrtc.ThreadUtils;
 
@@ -44,8 +45,6 @@ public class AppRTCAudioManager {
 
     private final Context apprtcContext;
     // Contains speakerphone setting: auto, true or false
-    @Nullable
-    private SpeakerPhonePreference speakerPhonePreference;
     // Handles all tasks related to Bluetooth headset devices.
     private final AppRTCBluetoothManager bluetoothManager;
     @Nullable
@@ -70,12 +69,7 @@ public class AppRTCAudioManager {
     // TODO(henrika): always set to AudioDevice.NONE today. Add support for
     // explicit selection based on choice by userSelectedAudioDevice.
     private CallIntegration.AudioDevice userSelectedAudioDevice;
-    // 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).
-    @Nullable
-    private AppRTCProximitySensor proximitySensor;
+
     // Contains a list of available audio devices. A Set collection is used to
     // avoid duplicate elements.
     private Set<CallIntegration.AudioDevice> audioDevices = new HashSet<>();
@@ -86,7 +80,6 @@ public class AppRTCAudioManager {
     private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
 
     public AppRTCAudioManager(final Context context) {
-        ThreadUtils.checkIsOnMainThread();
         apprtcContext = context;
         audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
         bluetoothManager = AppRTCBluetoothManager.create(context, this);
@@ -98,28 +91,10 @@ public class AppRTCAudioManager {
         } else {
             defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
         }
-        // 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,
-                // 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.
-                this::onProximitySensorChangedState);
         Log.d(Config.LOGTAG, "defaultAudioDevice: " + defaultAudioDevice);
         AppRTCUtils.logDeviceInfo(Config.LOGTAG);
     }
 
-    public void switchSpeakerPhonePreference(final SpeakerPhonePreference speakerPhonePreference) {
-        this.speakerPhonePreference = speakerPhonePreference;
-        if (speakerPhonePreference == SpeakerPhonePreference.EARPIECE && hasEarpiece()) {
-            defaultAudioDevice = CallIntegration.AudioDevice.EARPIECE;
-        } else {
-            defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
-        }
-        updateAudioDeviceState();
-    }
-
     public static boolean isMicrophoneAvailable() {
         microphoneLatch = new CountDownLatch(1);
         AudioRecord audioRecord = null;
@@ -156,30 +131,6 @@ public class AppRTCAudioManager {
         }
     }
 
-    /**
-     * 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() {
-        if (speakerPhonePreference != SpeakerPhonePreference.AUTO) {
-            return;
-        }
-        // The proximity sensor should only be activated when there are exactly two
-        // available audio devices.
-        if (audioDevices.size() == 2 && audioDevices.contains(CallIntegration.AudioDevice.EARPIECE)
-                && audioDevices.contains(CallIntegration.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".
-                setAudioDeviceInternal(CallIntegration.AudioDevice.EARPIECE);
-            } else {
-                // Sensor reports that a "handset is removed from a person's ear", or
-                // "the light sensor is no longer covered".
-                setAudioDeviceInternal(CallIntegration.AudioDevice.SPEAKER_PHONE);
-            }
-        }
-    }
-
     @SuppressWarnings("deprecation")
     public void start(AudioManagerEvents audioManagerEvents) {
         Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".start()");
@@ -280,6 +231,7 @@ public class AppRTCAudioManager {
 
     @SuppressWarnings("deprecation")
     public void stop() {
+        Log.d(Config.LOGTAG,"appRtpAudioManager.stop()");
         Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".stop()");
         ThreadUtils.checkIsOnMainThread();
         if (amState != AudioManagerState.RUNNING) {
@@ -296,12 +248,8 @@ public class AppRTCAudioManager {
         // Abandon audio focus. Gives the previous focus owner, if any, focus.
         audioManager.abandonAudioFocus(audioFocusChangeListener);
         audioFocusChangeListener = null;
-        Log.d(Config.LOGTAG, "Abandoned audio focus for VOICE_CALL streams");
-        if (proximitySensor != null) {
-            proximitySensor.stop();
-            proximitySensor = null;
-        }
         audioManagerEvents = null;
+        Log.d(Config.LOGTAG,"appRtpAudioManager.stopped()");
     }
 
     /**
@@ -375,7 +323,6 @@ public class AppRTCAudioManager {
      * Returns the currently selected audio device.
      */
     public CallIntegration.AudioDevice getSelectedAudioDevice() {
-        ThreadUtils.checkIsOnMainThread();
         return selectedAudioDevice;
     }
 
@@ -574,6 +521,10 @@ public class AppRTCAudioManager {
         Log.d(Config.LOGTAG, "--- updateAudioDeviceState done");
     }
 
+    public void executeOnMain(final Runnable runnable) {
+        ContextCompat.getMainExecutor(apprtcContext).execute(runnable);
+    }
+
     /**
      * AudioManager state.
      */
@@ -583,18 +534,6 @@ public class AppRTCAudioManager {
         RUNNING,
     }
 
-    public enum SpeakerPhonePreference {
-        AUTO, EARPIECE, SPEAKER;
-
-        public static SpeakerPhonePreference of(final Set<Media> media) {
-            if (media.contains(Media.VIDEO)) {
-                return SPEAKER;
-            } else {
-                return EARPIECE;
-            }
-        }
-    }
-
     /**
      * Selected audio device change event.
      */

src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java 🔗

@@ -68,8 +68,6 @@ public class AppRTCBluetoothManager {
             };
 
     protected AppRTCBluetoothManager(Context context, AppRTCAudioManager audioManager) {
-        Log.d(Config.LOGTAG, "ctor");
-        ThreadUtils.checkIsOnMainThread();
         apprtcContext = context;
         apprtcAudioManager = audioManager;
         this.audioManager = getAudioManager(context);

src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java 🔗

@@ -1,171 +0,0 @@
-/*
- *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree. An additional intellectual property rights grant can be found
- *  in the file PATENTS.  All contributing project authors may
- *  be found in the AUTHORS file in the root of the source tree.
- */
-package eu.siacs.conversations.services;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import org.webrtc.ThreadUtils;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.AppRTCUtils;
-
-/**
- * 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 {
-    // 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 ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker();
-    private final Runnable onSensorStateListener;
-    private final SensorManager sensorManager;
-    @Nullable
-    private Sensor proximitySensor;
-    private boolean lastStateReportIsNear;
-
-    private AppRTCProximitySensor(Context context, Runnable sensorStateListener) {
-        Log.d(Config.LOGTAG, "AppRTCProximitySensor" + AppRTCUtils.getThreadInfo());
-        onSensorStateListener = sensorStateListener;
-        sensorManager = ((SensorManager) context.getSystemService(Context.SENSOR_SERVICE));
-    }
-
-    /**
-     * Construction
-     */
-    static AppRTCProximitySensor create(Context context, Runnable sensorStateListener) {
-        return new AppRTCProximitySensor(context, sensorStateListener);
-    }
-
-    /**
-     * Activate the proximity sensor. Also do initialization if called for the
-     * first time.
-     */
-    public boolean start() {
-        threadChecker.checkIsOnValidThread();
-        Log.d(Config.LOGTAG, "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() {
-        threadChecker.checkIsOnValidThread();
-        Log.d(Config.LOGTAG, "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() {
-        threadChecker.checkIsOnValidThread();
-        return lastStateReportIsNear;
-    }
-
-    @Override
-    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
-        threadChecker.checkIsOnValidThread();
-        AppRTCUtils.assertIsTrue(sensor.getType() == Sensor.TYPE_PROXIMITY);
-        if (accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
-            Log.e(Config.LOGTAG, "The values returned by this sensor cannot be trusted");
-        }
-    }
-
-    @Override
-    public final void onSensorChanged(SensorEvent event) {
-        threadChecker.checkIsOnValidThread();
-        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(Config.LOGTAG, "Proximity sensor => NEAR state");
-            lastStateReportIsNear = true;
-        } else {
-            Log.d(Config.LOGTAG, "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(Config.LOGTAG, "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 returned 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;
-        }
-        StringBuilder info = new StringBuilder("Proximity sensor: ");
-        info.append("name=").append(proximitySensor.getName());
-        info.append(", vendor: ").append(proximitySensor.getVendor());
-        info.append(", power: ").append(proximitySensor.getPower());
-        info.append(", resolution: ").append(proximitySensor.getResolution());
-        info.append(", max range: ").append(proximitySensor.getMaximumRange());
-        info.append(", min delay: ").append(proximitySensor.getMinDelay());
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
-            // Added in API level 20.
-            info.append(", type: ").append(proximitySensor.getStringType());
-        }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            // Added in API level 21.
-            info.append(", max delay: ").append(proximitySensor.getMaxDelay());
-            info.append(", reporting mode: ").append(proximitySensor.getReportingMode());
-            info.append(", isWakeUpSensor: ").append(proximitySensor.isWakeUpSensor());
-        }
-        Log.d(Config.LOGTAG, info.toString());
-    }
-}

src/main/java/eu/siacs/conversations/services/CallIntegration.java 🔗

@@ -11,6 +11,7 @@ import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -42,8 +43,8 @@ public class CallIntegration extends Connection {
             this.appRTCAudioManager = null;
         } else {
             this.appRTCAudioManager = new AppRTCAudioManager(context);
-            this.appRTCAudioManager.start(this::onAudioDeviceChanged);
-            // TODO WebRTCWrapper would issue one call to  eventCallback.onAudioDeviceChanged
+            ContextCompat.getMainExecutor(context)
+                    .execute(() -> this.appRTCAudioManager.start(this::onAudioDeviceChanged));
         }
         setRingbackRequested(true);
     }
@@ -149,6 +150,12 @@ public class CallIntegration extends Connection {
         final var available = getAudioDevices();
         if (available.contains(audioDevice)) {
             this.setAudioDevice(audioDevice);
+        } else {
+            Log.d(
+                    Config.LOGTAG,
+                    "application requested to switch to "
+                            + audioDevice
+                            + " but device was not available");
         }
     }
 
@@ -269,7 +276,8 @@ public class CallIntegration extends Connection {
     }
 
     private void setAudioDeviceFallback(final AudioDevice audioDevice) {
-        requireAppRtcAudioManager().setDefaultAudioDevice(audioDevice);
+        final var audioManager = requireAppRtcAudioManager();
+        audioManager.executeOnMain(() -> audioManager.setDefaultAudioDevice(audioDevice));
     }
 
     @NonNull
@@ -287,7 +295,7 @@ public class CallIntegration extends Connection {
         if (state == STATE_DISCONNECTED) {
             final var audioManager = this.appRTCAudioManager;
             if (audioManager != null) {
-                audioManager.stop();
+                audioManager.executeOnMain(audioManager::stop);
             }
         }
     }
@@ -382,8 +390,11 @@ public class CallIntegration extends Connection {
             return;
         }
         final var audioManager = requireAppRtcAudioManager();
-        this.onAudioDeviceChanged(
-                audioManager.getSelectedAudioDevice(), audioManager.getAudioDevices());
+        audioManager.executeOnMain(
+                () ->
+                        this.onAudioDeviceChanged(
+                                audioManager.getSelectedAudioDevice(),
+                                audioManager.getAudioDevices()));
     }
 
     /** AudioDevice is the names of possible audio devices that we currently support. */

src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java 🔗

@@ -122,6 +122,7 @@ public class CallIntegrationConnectionService extends ConnectionService {
 
     public Connection onCreateIncomingConnection(
             final PhoneAccountHandle phoneAccountHandle, final ConnectionRequest request) {
+        Log.d(Config.LOGTAG, "onCreateIncomingConnection()");
         final var service = ServiceConnectionService.get(this.serviceFuture);
         final Bundle extras = request.getExtras();
         final Bundle extraExtras = extras.getBundle(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
@@ -182,7 +183,8 @@ public class CallIntegrationConnectionService extends ConnectionService {
     }
 
     public static void unregisterPhoneAccount(final Context context, final Account account) {
-        context.getSystemService(TelecomManager.class).unregisterPhoneAccount(getHandle(context, account));
+        context.getSystemService(TelecomManager.class)
+                .unregisterPhoneAccount(getHandle(context, account));
     }
 
     public static PhoneAccountHandle getHandle(final Context context, final Account account) {

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -2300,8 +2300,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
             final boolean trickle)
             throws WebRTCWrapper.InitializationException {
         this.jingleConnectionManager.ensureConnectionIsRegistered(this);
-        this.webRTCWrapper.setup(
-                this.xmppConnectionService, AppRTCAudioManager.SpeakerPhonePreference.of(media));
+        this.webRTCWrapper.setup(this.xmppConnectionService);
         this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
     }
 

src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java 🔗

@@ -222,9 +222,7 @@ public class WebRTCWrapper {
         }
     }
 
-    public void setup(
-            final XmppConnectionService service,
-            @Nonnull final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference)
+    public void setup(final XmppConnectionService service)
             throws InitializationException {
         try {
             PeerConnectionFactory.initialize(