From d79fc1bb790ef70476886167575349035f2ff9a0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Jan 2024 18:50:40 +0100 Subject: [PATCH] run some AppRTCAudioManager actions on main thread --- .../services/AppRTCAudioManager.java | 77 +------- .../services/AppRTCBluetoothManager.java | 2 - .../services/AppRTCProximitySensor.java | 171 ------------------ .../services/CallIntegration.java | 23 ++- .../CallIntegrationConnectionService.java | 4 +- .../xmpp/jingle/JingleRtpConnection.java | 3 +- .../xmpp/jingle/WebRTCWrapper.java | 4 +- 7 files changed, 30 insertions(+), 254 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java diff --git a/src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java b/src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java index a472445d3..894b2ace6 100644 --- a/src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java +++ b/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 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) { - if (media.contains(Media.VIDEO)) { - return SPEAKER; - } else { - return EARPIECE; - } - } - } - /** * Selected audio device change event. */ diff --git a/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java b/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java index 484072605..d915459a4 100644 --- a/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java b/src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java deleted file mode 100644 index 2f24787c0..000000000 --- a/src/main/java/eu/siacs/conversations/services/AppRTCProximitySensor.java +++ /dev/null @@ -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()); - } -} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index a4dffffaa..051a7de9a 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/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. */ diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java index 01f246844..e1a9b3383 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java +++ b/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) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index e9d750319..2cbd8c739 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index fa504ed19..128a35bf0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/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(