diff --git a/app/build.gradle b/app/build.gradle
index bf0c91444..900d50f30 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,7 +39,7 @@ android {
buildToolsVersion '33.0.0'
defaultConfig {
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index afb39eef3..e3255e82a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -37,8 +37,11 @@
-
+
+
@@ -122,6 +126,8 @@
+
+
@@ -180,7 +186,8 @@
android:name=".messagesearch.MessageSearchActivity"
android:theme="@style/AppTheme" />
-
+
@@ -203,7 +210,8 @@
android:resource="@xml/contacts" />
-
+
diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
index 1b78b4714..e554d2dd4 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
+++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
@@ -30,6 +30,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.Icon;
@@ -93,7 +94,7 @@ import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.power.PowerManagerUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
-import com.nextcloud.talk.webrtc.MagicAudioManager;
+import com.nextcloud.talk.webrtc.WebRtcAudioManger;
import com.nextcloud.talk.webrtc.MagicWebRTCUtils;
import com.nextcloud.talk.webrtc.MagicWebSocketInstance;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
@@ -138,12 +139,15 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import autodagger.AutoInjector;
import io.reactivex.Observable;
@@ -157,6 +161,7 @@ import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
import okhttp3.Cache;
import pub.devrel.easypermissions.AfterPermissionGranted;
+import static android.app.PendingIntent.FLAG_MUTABLE;
import static com.nextcloud.talk.webrtc.Globals.JOB_ID;
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
@@ -182,11 +187,11 @@ public class CallActivity extends CallBaseActivity {
public static final String TAG = "CallActivity";
- public MagicAudioManager audioManager;
+ public WebRtcAudioManger audioManager;
private static final String[] PERMISSIONS_CALL = {
- android.Manifest.permission.CAMERA,
- android.Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO
};
private static final String[] PERMISSIONS_CAMERA = {
@@ -268,6 +273,13 @@ public class CallActivity extends CallBaseActivity {
private AudioOutputDialog audioOutputDialog;
+ private final ActivityResultLauncher requestBluetoothPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
+ if (isGranted) {
+ enableBluetoothManager();
+ }
+ });
+
@SuppressLint("ClickableViewAccessibility")
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -317,6 +329,9 @@ public class CallActivity extends CallBaseActivity {
.setRepeatCount(PulseAnimation.INFINITE)
.setRepeatMode(PulseAnimation.REVERSE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ requestBluetoothPermission();
+ }
basicInitialization();
participantDisplayItems = new HashMap<>();
initViews();
@@ -326,6 +341,22 @@ public class CallActivity extends CallBaseActivity {
updateSelfVideoViewPosition();
}
+ @SuppressLint("InlinedApi")
+ @RequiresApi(api = Build.VERSION_CODES.S)
+ private void requestBluetoothPermission() {
+ if (ContextCompat.checkSelfPermission(
+ getContext(), Manifest.permission.BLUETOOTH_CONNECT) ==
+ PackageManager.PERMISSION_DENIED) {
+ requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ }
+
+ private void enableBluetoothManager() {
+ if (audioManager != null) {
+ audioManager.startBluetoothManager();
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -419,16 +450,16 @@ public class CallActivity extends CallBaseActivity {
// Create and audio manager that will take care of audio routing,
// audio modes, audio device enumeration etc.
- audioManager = MagicAudioManager.create(getApplicationContext(), isVoiceOnlyCall);
+ audioManager = WebRtcAudioManger.create(getApplicationContext(), isVoiceOnlyCall);
// Store existing audio settings and change audio mode to
// MODE_IN_COMMUNICATION for best possible VoIP performance.
Log.d(TAG, "Starting the audio manager...");
audioManager.start(this::onAudioManagerDevicesChanged);
if (isVoiceOnlyCall) {
- setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE);
+ setAudioOutputChannel(WebRtcAudioManger.AudioDevice.EARPIECE);
} else {
- setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
+ setAudioOutputChannel(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE);
}
iceServers = new ArrayList<>();
@@ -462,14 +493,14 @@ public class CallActivity extends CallBaseActivity {
microphoneInitialization();
}
- public void setAudioOutputChannel(MagicAudioManager.AudioDevice selectedAudioDevice) {
+ public void setAudioOutputChannel(WebRtcAudioManger.AudioDevice selectedAudioDevice) {
if (audioManager != null) {
audioManager.selectAudioDevice(selectedAudioDevice);
updateAudioOutputButton(audioManager.getCurrentAudioDevice());
}
}
- private void updateAudioOutputButton(MagicAudioManager.AudioDevice activeAudioDevice) {
+ private void updateAudioOutputButton(WebRtcAudioManger.AudioDevice activeAudioDevice) {
switch (activeAudioDevice) {
case BLUETOOTH:
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
@@ -763,14 +794,14 @@ public class CallActivity extends CallBaseActivity {
}
private void onAudioManagerDevicesChanged(
- final MagicAudioManager.AudioDevice currentDevice,
- final Set availableDevices) {
+ final WebRtcAudioManger.AudioDevice currentDevice,
+ final Set availableDevices) {
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
+ "currentDevice: " + currentDevice);
- final boolean shouldDisableProximityLock = (currentDevice.equals(MagicAudioManager.AudioDevice.WIRED_HEADSET)
- || currentDevice.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
- || currentDevice.equals(MagicAudioManager.AudioDevice.BLUETOOTH));
+ final boolean shouldDisableProximityLock = (currentDevice.equals(WebRtcAudioManger.AudioDevice.WIRED_HEADSET)
+ || currentDevice.equals(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE)
+ || currentDevice.equals(WebRtcAudioManger.AudioDevice.BLUETOOTH));
if (shouldDisableProximityLock) {
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
@@ -2580,7 +2611,7 @@ public class CallActivity extends CallBaseActivity {
this,
requestCode,
new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode),
- 0);
+ FLAG_MUTABLE);
actions.add(new RemoteAction(icon, title, title, intent));
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt
index 2876745eb..ff394bcd8 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt
@@ -30,7 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.databinding.DialogAudioOutputBinding
-import com.nextcloud.talk.webrtc.MagicAudioManager
+import com.nextcloud.talk.webrtc.WebRtcAudioManger
class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(callActivity) {
@@ -47,26 +47,26 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
}
fun updateOutputDeviceList() {
- if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.BLUETOOTH) == false) {
+ if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManger.AudioDevice.BLUETOOTH) == false) {
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.VISIBLE
}
- if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.EARPIECE) == false) {
+ if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManger.AudioDevice.EARPIECE) == false) {
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.VISIBLE
}
- if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.SPEAKER_PHONE) == false) {
+ if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE) == false) {
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.VISIBLE
}
if (callActivity.audioManager?.currentAudioDevice?.equals(
- MagicAudioManager.AudioDevice.WIRED_HEADSET
+ WebRtcAudioManger.AudioDevice.WIRED_HEADSET
) == true
) {
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
@@ -81,7 +81,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
private fun highlightActiveOutputChannel() {
when (callActivity.audioManager?.currentAudioDevice) {
- MagicAudioManager.AudioDevice.BLUETOOTH -> {
+ WebRtcAudioManger.AudioDevice.BLUETOOTH -> {
dialogAudioOutputBinding.audioOutputBluetoothIcon.setColorFilter(
ContextCompat.getColor(
context,
@@ -94,7 +94,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
)
}
- MagicAudioManager.AudioDevice.SPEAKER_PHONE -> {
+ WebRtcAudioManger.AudioDevice.SPEAKER_PHONE -> {
dialogAudioOutputBinding.audioOutputSpeakerIcon.setColorFilter(
ContextCompat.getColor(
context,
@@ -107,7 +107,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
)
}
- MagicAudioManager.AudioDevice.EARPIECE -> {
+ WebRtcAudioManger.AudioDevice.EARPIECE -> {
dialogAudioOutputBinding.audioOutputEarspeakerIcon.setColorFilter(
ContextCompat.getColor(
context,
@@ -120,7 +120,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
)
}
- MagicAudioManager.AudioDevice.WIRED_HEADSET -> {
+ WebRtcAudioManger.AudioDevice.WIRED_HEADSET -> {
dialogAudioOutputBinding.audioOutputWiredHeadsetIcon.setColorFilter(
ContextCompat.getColor(
context,
@@ -139,17 +139,17 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
private fun initClickListeners() {
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
- callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.BLUETOOTH)
+ callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.BLUETOOTH)
dismiss()
}
dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
- callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
+ callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE)
dismiss()
}
dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
- callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE)
+ callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.EARPIECE)
dismiss()
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManger.java
similarity index 90%
rename from app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java
rename to app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManger.java
index 866e5c6ce..0bf784d49 100644
--- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java
+++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManger.java
@@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @author Mario Danic
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger
* Copyright (C) 2017 Mario Danic
*
* This program is free software: you can redistribute it and/or modify
@@ -52,15 +54,12 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-/**
- * MagicAudioManager manages all audio related parts of the AppRTC demo.
- */
-public class MagicAudioManager {
- private static final String TAG = "MagicAudioManager";
+public class WebRtcAudioManger {
+ private static final String TAG = WebRtcAudioManger.class.getCanonicalName();
private final Context magicContext;
- private final MagicBluetoothManager bluetoothManager;
- private boolean useProximitySensor;
- private AudioManager audioManager;
+ private final WebRtcBluetoothManager bluetoothManager;
+ private final boolean useProximitySensor;
+ private final AudioManager audioManager;
private AudioManagerListener audioManagerListener;
private AudioManagerState amState;
private int savedAudioMode = AudioManager.MODE_INVALID;
@@ -75,17 +74,17 @@ public class MagicAudioManager {
private Set audioDevices = new HashSet<>();
- private BroadcastReceiver wiredHeadsetReceiver;
+ private final BroadcastReceiver wiredHeadsetReceiver;
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
- private PowerManagerUtils powerManagerUtils;
+ private final PowerManagerUtils powerManagerUtils;
- private MagicAudioManager(Context context, boolean useProximitySensor) {
+ private WebRtcAudioManger(Context context, boolean useProximitySensor) {
Log.d(TAG, "ctor");
ThreadUtils.checkIsOnMainThread();
magicContext = context;
audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
- bluetoothManager = MagicBluetoothManager.create(context, this);
+ bluetoothManager = WebRtcBluetoothManager.create(context, this);
wiredHeadsetReceiver = new WiredHeadsetReceiver();
amState = AudioManagerState.UNINITIALIZED;
@@ -111,8 +110,14 @@ public class MagicAudioManager {
/**
* Construction.
*/
- public static MagicAudioManager create(Context context, boolean useProximitySensor) {
- return new MagicAudioManager(context, useProximitySensor);
+ public static WebRtcAudioManger create(Context context, boolean useProximitySensor) {
+ return new WebRtcAudioManger(context, useProximitySensor);
+ }
+
+ public void startBluetoothManager() {
+ // Initialize and start Bluetooth if a BT device is available or initiate
+ // detection of new (enabled) BT devices.
+ bluetoothManager.start();
}
/**
@@ -136,7 +141,7 @@ public class MagicAudioManager {
.SENSOR_NEAR, null, null, null, null));
} else {
- setAudioDeviceInternal(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
+ setAudioDeviceInternal(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE);
Log.d(TAG, "switched to SPEAKER_PHONE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=far");
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
@@ -228,9 +233,7 @@ public class MagicAudioManager {
currentAudioDevice = AudioDevice.NONE;
audioDevices.clear();
- // Initialize and start Bluetooth if a BT device is available or initiate
- // detection of new (enabled) BT devices.
- bluetoothManager.start();
+ startBluetoothManager();
// Do initial selection of audio device. This setting can later be changed
// either by adding/removing a BT or wired headset or by covering/uncovering
@@ -256,7 +259,9 @@ public class MagicAudioManager {
unregisterReceiver(wiredHeadsetReceiver);
- bluetoothManager.stop();
+ if(bluetoothManager.started()) {
+ bluetoothManager.stop();
+ }
// Restore previously stored audio states.
setSpeakerphoneOn(savedIsSpeakerPhoneOn);
@@ -411,17 +416,17 @@ public class MagicAudioManager {
+ "current=" + currentAudioDevice + ", "
+ "user selected=" + userSelectedAudioDevice);
- if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
- || bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
- || bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
+ if (bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_UNAVAILABLE
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_DISCONNECTING) {
bluetoothManager.updateDevice();
}
Set newAudioDevices = new HashSet<>();
- if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
- || bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING
- || bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE) {
+ if (bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE) {
newAudioDevices.add(AudioDevice.BLUETOOTH);
}
@@ -441,7 +446,7 @@ public class MagicAudioManager {
// Correct user selected audio devices if needed.
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH
- && bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE) {
+ && bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_UNAVAILABLE) {
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
}
if (userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE && hasWiredHeadset) {
@@ -455,21 +460,21 @@ public class MagicAudioManager {
// Need to start Bluetooth if it is available and user either selected it explicitly or
// user did not select any output device.
boolean needBluetoothAudioStart =
- bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
+ bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
&& (userSelectedAudioDevice == AudioDevice.NONE
|| userSelectedAudioDevice == AudioDevice.BLUETOOTH);
// Need to stop Bluetooth audio if user selected different device and
// Bluetooth SCO connection is established or in the process.
boolean needBluetoothAudioStop =
- (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
- || bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING)
+ (bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING)
&& (userSelectedAudioDevice != AudioDevice.NONE
&& userSelectedAudioDevice != AudioDevice.BLUETOOTH);
- if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
- || bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING
- || bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
+ if (bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING
+ || bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED) {
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
+ "stop=" + needBluetoothAudioStop + ", "
+ "BT state=" + bluetoothManager.getState());
@@ -494,7 +499,7 @@ public class MagicAudioManager {
// Update selected audio device.
AudioDevice newCurrentAudioDevice;
- if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
+ if (bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED) {
// If a Bluetooth is connected, then it should be used as output audio
// device. Note that it is not sufficient that a headset is available;
// an active SCO channel must also be up and running.
diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcBluetoothManager.java
similarity index 91%
rename from app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java
rename to app/src/main/java/com/nextcloud/talk/webrtc/WebRtcBluetoothManager.java
index 2d067298b..0509efc83 100644
--- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java
+++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcBluetoothManager.java
@@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @author Mario Danic
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger
* Copyright (C) 2017 Mario Danic
*
* This program is free software: you can redistribute it and/or modify
@@ -31,6 +33,7 @@
package com.nextcloud.talk.webrtc;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -47,20 +50,23 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
+
import org.webrtc.ThreadUtils;
import java.util.List;
import java.util.Set;
-public class MagicBluetoothManager {
- private static final String TAG = "MagicBluetoothManager";
+import androidx.core.app.ActivityCompat;
+
+public class WebRtcBluetoothManager {
+ private static final String TAG = WebRtcBluetoothManager.class.getCanonicalName();
// Timeout interval for starting or stopping audio to a Bluetooth SCO device.
private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
// Maximum number of SCO connection attempts.
private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
private final Context apprtcContext;
- private final MagicAudioManager apprtcAudioManager;
+ private final WebRtcAudioManger webRtcAudioManager;
private final AudioManager audioManager;
private final Handler handler;
private final BluetoothProfile.ServiceListener bluetoothServiceListener;
@@ -73,18 +79,14 @@ public class MagicBluetoothManager {
// Runs when the Bluetooth timeout expires. We use that timeout after calling
// startScoAudio() or stopScoAudio() because we're not guaranteed to get a
// callback after those calls.
- private final Runnable bluetoothTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- bluetoothTimeout();
- }
- };
+ private final Runnable bluetoothTimeoutRunnable = this::bluetoothTimeout;
+ private boolean started = false;
- protected MagicBluetoothManager(Context context, MagicAudioManager audioManager) {
+ protected WebRtcBluetoothManager(Context context, WebRtcAudioManger audioManager) {
Log.d(TAG, "ctor");
ThreadUtils.checkIsOnMainThread();
apprtcContext = context;
- apprtcAudioManager = audioManager;
+ webRtcAudioManager = audioManager;
this.audioManager = getAudioManager(context);
bluetoothState = State.UNINITIALIZED;
bluetoothServiceListener = new BluetoothServiceListener();
@@ -95,8 +97,8 @@ public class MagicBluetoothManager {
/**
* Construction.
*/
- static MagicBluetoothManager create(Context context, MagicAudioManager audioManager) {
- return new MagicBluetoothManager(context, audioManager);
+ static WebRtcBluetoothManager create(Context context, WebRtcAudioManger audioManager) {
+ return new WebRtcBluetoothManager(context, audioManager);
}
/**
@@ -122,11 +124,11 @@ public class MagicBluetoothManager {
* Note that the MagicAudioManager is also involved in driving this state
* change.
*/
+ @SuppressLint("MissingPermission")
public void start() {
ThreadUtils.checkIsOnMainThread();
Log.d(TAG, "start");
- if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) {
- Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission");
+ if(hasNoBluetoothPermission()){
return;
}
if (bluetoothState != State.UNINITIALIZED) {
@@ -166,6 +168,7 @@ public class MagicBluetoothManager {
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
Log.d(TAG, "Bluetooth proxy for headset profile has started");
bluetoothState = State.HEADSET_UNAVAILABLE;
+ started = true;
Log.d(TAG, "start done: BT state=" + bluetoothState);
}
@@ -262,8 +265,12 @@ public class MagicBluetoothManager {
* HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected
* device if available.
*/
+ @SuppressLint("MissingPermission")
public void updateDevice() {
- if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
+ boolean hasNoBluetoothPermissions = hasNoBluetoothPermission();
+ if (hasNoBluetoothPermissions ||
+ bluetoothState == State.UNINITIALIZED ||
+ bluetoothHeadset == null) {
return;
}
Log.d(TAG, "updateDevice");
@@ -307,16 +314,28 @@ public class MagicBluetoothManager {
return bluetoothAdapter.getProfileProxy(context, listener, profile);
}
- protected boolean hasPermission(Context context, String permission) {
- return apprtcContext.checkPermission(permission, Process.myPid(), Process.myUid())
- == PackageManager.PERMISSION_GRANTED;
+ private boolean hasNoBluetoothPermission() {
+ String permission;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permission = Manifest.permission.BLUETOOTH_CONNECT;
+ } else {
+ permission = Manifest.permission.BLUETOOTH;
+ }
+
+ boolean hasPermission =
+ ActivityCompat.checkSelfPermission(apprtcContext, permission) == PackageManager.PERMISSION_GRANTED;
+ if(!hasPermission) {
+ Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks \"" + permission + "\" permission");
+ }
+ return !hasPermission;
}
/**
* Logs the state of the local Bluetooth adapter.
*/
- @SuppressLint("HardwareIds")
- protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
+ @SuppressLint({"HardwareIds", "MissingPermission"})
+ private void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
Log.d(TAG, "BluetoothAdapter: "
+ "enabled=" + localAdapter.isEnabled() + ", "
+ "state=" + stateToString(localAdapter.getState()) + ", "
@@ -337,7 +356,7 @@ public class MagicBluetoothManager {
private void updateAudioDeviceState() {
ThreadUtils.checkIsOnMainThread();
Log.d(TAG, "updateAudioDeviceState");
- apprtcAudioManager.updateAudioDeviceState();
+ webRtcAudioManager.updateAudioDeviceState();
}
/**
@@ -362,9 +381,13 @@ public class MagicBluetoothManager {
* Called when start of the BT SCO channel takes too long time. Usually
* happens when the BT device has been turned on during an ongoing call.
*/
+ @SuppressLint("MissingPermission")
private void bluetoothTimeout() {
ThreadUtils.checkIsOnMainThread();
- if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
+ boolean hasNoBluetoothPermissions = hasNoBluetoothPermission();
+ if (hasNoBluetoothPermissions ||
+ bluetoothState == State.UNINITIALIZED ||
+ bluetoothHeadset == null) {
return;
}
Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
@@ -435,6 +458,10 @@ public class MagicBluetoothManager {
}
}
+ public boolean started() {
+ return started;
+ }
+
// Bluetooth connection state.
public enum State {
// Bluetooth is not available; no adapter or Bluetooth is off.