From 4821b02729ee7fc03b98bb7fe207b0a6c7fc5c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 9 Jun 2022 17:39:09 +0200 Subject: [PATCH 1/5] Add Android build switch for checking bluetooth permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored method 'hasPermission' to 'hasNoBluetoothPermission'. The new method respect the in SKD 31 introduced permission BLUETOOTH_CONNECT. For older SDK versions the permission BLUETOOTH will be used. Additionaly make methods private which need bluetooth permissions and only called locally. For public methods which need bluetooth permissions the method 'hasNoBluetoothPermission' is called to check them. Also add suppress lint annotation for 'MissingPermission'. From SDK 30 to SDK 31 the bluetooth related permissions changed and it is suggested to add the attribute 'android:maxSdkVersion="30"' to the legacy BLUETOOTH permission in the 'AndroidManifest.xml' [1]: > For your legacy Bluetooth-related permission declarations, set > android:maxSdkVersion to 30. This app compatibility step helps the system > grant your app only the Bluetooth permissions that it needs when installed > on devices that run Android 12 or higher. This is explicitly not done here! During runtime (on Android 12) while starting the 'MagicBluetoothManger' the following part in 'android.bluetooth.BluetootHeadset' constructor will be executed and results in the previous exception: // Preserve legacy compatibility where apps were depending on // registerStateChangeCallback() performing a permissions check which // has been relaxed in modern platform versions if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Need BLUETOOTH permission"); } In the 'build.gradle' the 'targetSdkVersion 30' and 'compileSdkVersion 31' is configured. Because the 'MagicBluetoothManager' checks for the 'targetSdkVersion' instead of 'Build.VERSION.SDK_INT' also on a Android 12 (SDK 31) the 'BLUETOOTH' permission is needed. So the solution is to don't set `android:maxSdkVersion="30"' for the BLUETOOTH permission and request the BLUETOOTH_CONNECT permission. Resolves: #2132 See: [1] https://web.archive.org/web/20220416121005/https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android12-or-higher Signed-off-by: Tim Krüger --- .../talk/webrtc/MagicBluetoothManager.java | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java index 2d067298b..3c7651af4 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java @@ -31,6 +31,7 @@ package com.nextcloud.talk.webrtc; +import android.Manifest; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -47,11 +48,14 @@ 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; +import androidx.core.app.ActivityCompat; + public class MagicBluetoothManager { private static final String TAG = "MagicBluetoothManager"; @@ -73,12 +77,7 @@ 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; protected MagicBluetoothManager(Context context, MagicAudioManager audioManager) { Log.d(TAG, "ctor"); @@ -122,11 +121,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) { @@ -262,8 +261,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 +310,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()) + ", " @@ -362,9 +377,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 + ", " From dedbe40cc0a24df13029cb84053f304355b7f2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Wed, 15 Jun 2022 13:38:36 +0200 Subject: [PATCH 2/5] Launch 'MagicBluetoothManager' independently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change is needed to make the 'MagicBluetoothManager' startable after the from Android SDK 31 introduced BLUETOOTH_CONNECT is granted by the user. Before this change the 'MagicBluetoothManager' was only started in 'MagicAudioManager#start'. Now the new method 'MagicAudioManager#startBluetoothManager' can be used to start the 'MagicBluetoothManager'. This change is also a preperation to fix #1309 and #2114. See: #2132, #1309, #2124 Signed-off-by: Tim Krüger --- .../talk/webrtc/MagicAudioManager.java | 31 +++++++++++-------- .../talk/webrtc/MagicBluetoothManager.java | 10 +++++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java index 866e5c6ce..8e59a2474 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.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"; + private static final String TAG = MagicAudioManager.class.getCanonicalName(); private final Context magicContext; private final MagicBluetoothManager bluetoothManager; - private boolean useProximitySensor; - private AudioManager audioManager; + private final boolean useProximitySensor; + private final AudioManager audioManager; private AudioManagerListener audioManagerListener; private AudioManagerState amState; private int savedAudioMode = AudioManager.MODE_INVALID; @@ -75,10 +74,10 @@ 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) { Log.d(TAG, "ctor"); @@ -112,7 +111,13 @@ public class MagicAudioManager { * Construction. */ public static MagicAudioManager create(Context context, boolean useProximitySensor) { - return new MagicAudioManager(context, useProximitySensor); + return new MagicAudioManager(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(); } /** @@ -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); diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java index 3c7651af4..d0e9f2c33 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.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 @@ -57,7 +59,7 @@ import java.util.Set; import androidx.core.app.ActivityCompat; public class MagicBluetoothManager { - private static final String TAG = "MagicBluetoothManager"; + private static final String TAG = MagicBluetoothManager.class.getCanonicalName(); // Timeout interval for starting or stopping audio to a Bluetooth SCO device. private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000; @@ -78,6 +80,7 @@ public class MagicBluetoothManager { // startScoAudio() or stopScoAudio() because we're not guaranteed to get a // callback after those calls. private final Runnable bluetoothTimeoutRunnable = this::bluetoothTimeout; + private boolean started = false; protected MagicBluetoothManager(Context context, MagicAudioManager audioManager) { Log.d(TAG, "ctor"); @@ -165,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); } @@ -454,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. From e14f00fae772960525c948b0a198b6b4257038fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Mon, 20 Jun 2022 11:53:25 +0200 Subject: [PATCH 3/5] Request 'BLUETOOTH_CONNECT' permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Request the 'BLUETOOTH_CONNECT' permissions if not already granted. If the permission is be granted in this request, the 'MagicBluetoothManger' will be started. See: #2132 Signed-off-by: Tim Krüger --- .../talk/activities/CallActivity.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) 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..75215d82e 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; @@ -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; @@ -185,8 +189,8 @@ public class CallActivity extends CallBaseActivity { public MagicAudioManager 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 +272,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 +328,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 +340,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(); From 6e4841ae3a5b3393726e042d1dbca24d78e88053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Mon, 20 Jun 2022 12:02:29 +0200 Subject: [PATCH 4/5] Rename 'MagicAudioManager' and 'MagicBluetoothManager' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's not magic but WebRtc related. Signed-off-by: Tim Krüger --- .../talk/activities/CallActivity.java | 24 +++++----- .../talk/ui/dialog/AudioOutputDialog.kt | 24 +++++----- ...dioManager.java => WebRtcAudioManger.java} | 44 +++++++++---------- ...nager.java => WebRtcBluetoothManager.java} | 16 +++---- 4 files changed, 54 insertions(+), 54 deletions(-) rename app/src/main/java/com/nextcloud/talk/webrtc/{MagicAudioManager.java => WebRtcAudioManger.java} (92%) rename app/src/main/java/com/nextcloud/talk/webrtc/{MagicBluetoothManager.java => WebRtcBluetoothManager.java} (97%) 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 75215d82e..33a166046 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -94,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; @@ -186,7 +186,7 @@ public class CallActivity extends CallBaseActivity { public static final String TAG = "CallActivity"; - public MagicAudioManager audioManager; + public WebRtcAudioManger audioManager; private static final String[] PERMISSIONS_CALL = { Manifest.permission.CAMERA, @@ -449,16 +449,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<>(); @@ -492,14 +492,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( @@ -793,14 +793,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); 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 92% 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 8e59a2474..0bf784d49 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManger.java @@ -54,10 +54,10 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -public class MagicAudioManager { - private static final String TAG = MagicAudioManager.class.getCanonicalName(); +public class WebRtcAudioManger { + private static final String TAG = WebRtcAudioManger.class.getCanonicalName(); private final Context magicContext; - private final MagicBluetoothManager bluetoothManager; + private final WebRtcBluetoothManager bluetoothManager; private final boolean useProximitySensor; private final AudioManager audioManager; private AudioManagerListener audioManagerListener; @@ -79,12 +79,12 @@ public class MagicAudioManager { 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; @@ -110,8 +110,8 @@ 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() { @@ -141,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 @@ -416,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); } @@ -446,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) { @@ -460,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()); @@ -499,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 97% 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 d0e9f2c33..0509efc83 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicBluetoothManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcBluetoothManager.java @@ -58,15 +58,15 @@ import java.util.Set; import androidx.core.app.ActivityCompat; -public class MagicBluetoothManager { - private static final String TAG = MagicBluetoothManager.class.getCanonicalName(); +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; @@ -82,11 +82,11 @@ public class MagicBluetoothManager { 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(); @@ -97,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); } /** @@ -356,7 +356,7 @@ public class MagicBluetoothManager { private void updateAudioDeviceState() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "updateAudioDeviceState"); - apprtcAudioManager.updateAudioDeviceState(); + webRtcAudioManager.updateAudioDeviceState(); } /** From 1f936cb677ed17f93fba461ae59ac84bda5e99db Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 22 Jun 2022 10:59:38 +0200 Subject: [PATCH 5/5] migrate to sdk=31 Signed-off-by: Andy Scherzinger --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 14 +++++++++++--- .../nextcloud/talk/activities/CallActivity.java | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 296dc7eb5..26fdca39d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,7 +39,7 @@ android { buildToolsVersion '32.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 a098c8ff9..92c56df14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,8 +37,11 @@ - + + @@ -122,6 +126,8 @@ + + @@ -176,7 +182,8 @@ android:name=".messagesearch.MessageSearchActivity" android:theme="@style/AppTheme" /> - + @@ -199,7 +206,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 33a166046..e554d2dd4 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -161,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; @@ -2610,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));