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 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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)); From 98a96ef6e4092423f800c978a5df47130e9c3601 Mon Sep 17 00:00:00 2001 From: drone Date: Wed, 22 Jun 2022 09:05:58 +0000 Subject: [PATCH 06/13] Drone: update Lint results to reflect reduced error/warning count [skip ci] Signed-off-by: drone --- scripts/analysis/lint-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 9ab1d14f7..5570559e9 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 2 errors and 123 warnings + Lint Report: 1 error and 120 warnings From 3096d90bc6428ab6d1368568e9b8e3eb75df03e8 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 22 Jun 2022 11:21:46 +0200 Subject: [PATCH 07/13] only set intent flag for sdk>=31 Signed-off-by: Andy Scherzinger --- .../talk/activities/CallActivity.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 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 e554d2dd4..a622dc992 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -2606,12 +2606,22 @@ public class CallActivity extends CallBaseActivity { final ArrayList actions = new ArrayList<>(); final Icon icon = Icon.createWithResource(this, iconId); - final PendingIntent intent = - PendingIntent.getBroadcast( - this, - requestCode, - new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), - FLAG_MUTABLE); + PendingIntent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intent = + PendingIntent.getBroadcast( + this, + requestCode, + new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), + FLAG_MUTABLE); + } else { + intent = + PendingIntent.getBroadcast( + this, + requestCode, + new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), + 0); + } actions.add(new RemoteAction(icon, title, title, intent)); From f366c75f68be62d31702c5bcbe14e22548627245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Wed, 22 Jun 2022 15:33:26 +0200 Subject: [PATCH 08/13] Add mandetory intent flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since SDK 31 for an 'PendingIntent' the intent flags 'FLAG_IMMUTABLE' or 'FLAG_IMMUTABLE' are mandetory. Signed-off-by: Tim Krüger --- .../firebase/MagicFirebaseMessagingService.kt | 10 ++++++++-- .../nextcloud/talk/jobs/NotificationWorker.java | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt index 02668fb98..86cd47dde 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -24,6 +24,7 @@ import android.app.Notification import android.app.PendingIntent import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.os.Handler import android.util.Base64 @@ -168,7 +169,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { Log.d(NotificationWorker.TAG, "Invalid private key " + e1.localizedMessage) } } catch (exception: Exception) { - Log.d(NotificationWorker.TAG, "Something went very wrong " + exception.localizedMessage) + Log.d(NotificationWorker.TAG, "Something went very wrong " + exception.localizedMessage, exception) } } @@ -217,7 +218,11 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { this@MagicFirebaseMessagingService, 0, fullScreenIntent, - PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } ) val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences!!) @@ -319,6 +324,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { override fun onError(e: Throwable) { // unused atm } + override fun onComplete() { stopForeground(true) handler.removeCallbacksAndMessages(null) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index ff241bf72..de5702bc7 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -295,7 +295,13 @@ public class NotificationWorker extends Worker { // Use unique request code to make sure that a new PendingIntent gets created for each notification // See https://github.com/nextcloud/talk-android/issues/2111 int requestCode = (int) System.currentTimeMillis(); - PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, 0); + int intentFlag; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlag = PendingIntent.FLAG_MUTABLE; + } else { + intentFlag = 0; + } + PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, intentFlag); Uri uri = Uri.parse(signatureVerification.getUserEntity().getBaseUrl()); String baseUrl = uri.getHost(); @@ -422,8 +428,15 @@ public class NotificationWorker extends Worker { // It is NOT the same as the notification ID used in communication with the server. actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_SYSTEM_NOTIFICATION_ID(), systemNotificationId); actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId()); + + int intentFlag; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlag = PendingIntent.FLAG_MUTABLE|PendingIntent.FLAG_UPDATE_CURRENT; + } else { + intentFlag = PendingIntent.FLAG_UPDATE_CURRENT; + } PendingIntent replyPendingIntent = - PendingIntent.getBroadcast(context, systemNotificationId, actualIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.getBroadcast(context, systemNotificationId, actualIntent, intentFlag); NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply, replyLabel, replyPendingIntent) From 1551e7f1cb48edcb622ed7015ccef750e1ead793 Mon Sep 17 00:00:00 2001 From: drone Date: Wed, 22 Jun 2022 13:49:22 +0000 Subject: [PATCH 09/13] Drone: update Lint results to reflect reduced error/warning count [skip ci] Signed-off-by: drone --- scripts/analysis/lint-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 5570559e9..dbbd57b59 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 1 error and 120 warnings + Lint Report: 1 error and 117 warnings From 738ed94f986c86555cb6ac6324df73a4f5b70b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Wed, 22 Jun 2022 15:44:16 +0200 Subject: [PATCH 10/13] Add foreground service type to MagicFirebaseMessagingService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since SDK 31 foreground services can't start while running in background, except for a few special cases like phone calls. See: - https://developer.android.com/guide/components/foreground-services#background-start-restrictions Signed-off-by: Tim Krüger --- app/src/gplay/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/gplay/AndroidManifest.xml b/app/src/gplay/AndroidManifest.xml index 9a878b36f..432d4f9c1 100644 --- a/app/src/gplay/AndroidManifest.xml +++ b/app/src/gplay/AndroidManifest.xml @@ -39,7 +39,8 @@ + android:exported="false" + android:foregroundServiceType="phoneCall"> From 331309405da87724bf05804f515516557f224aa1 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 22 Jun 2022 10:59:38 +0200 Subject: [PATCH 11/13] Set intent flag required by Android 12 (SDK 31) Signed-off-by: Andy Scherzinger --- .../talk/activities/CallActivity.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 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 a622dc992..191e5777c 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 android.app.PendingIntent.FLAG_MUTABLE; import static com.nextcloud.talk.webrtc.Globals.JOB_ID; import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE; @@ -2606,22 +2607,19 @@ public class CallActivity extends CallBaseActivity { final ArrayList actions = new ArrayList<>(); final Icon icon = Icon.createWithResource(this, iconId); - PendingIntent intent; + + int intentFlag; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - intent = - PendingIntent.getBroadcast( - this, - requestCode, - new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), - FLAG_MUTABLE); + intentFlag = FLAG_MUTABLE; } else { - intent = - PendingIntent.getBroadcast( - this, - requestCode, - new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), - 0); + intentFlag = 0; } + final PendingIntent intent = + PendingIntent.getBroadcast( + this, + requestCode, + new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode), + intentFlag); actions.add(new RemoteAction(icon, title, title, intent)); From 5b5db208ee09e928fd66a4b836f9dff127999647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 23 Jun 2022 10:00:21 +0200 Subject: [PATCH 12/13] Rename 'MagicFirebaseMessagingService' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed the service to 'ChatAndCallMessagingService' to respect that the service handles chat messages and calls. Signed-off-by: Tim Krüger --- app/src/gplay/AndroidManifest.xml | 4 +++- ...MessagingService.kt => ChatAndCallMessagingService.kt} | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) rename app/src/gplay/java/com/nextcloud/talk/services/firebase/{MagicFirebaseMessagingService.kt => ChatAndCallMessagingService.kt} (97%) diff --git a/app/src/gplay/AndroidManifest.xml b/app/src/gplay/AndroidManifest.xml index 432d4f9c1..794a2c8a1 100644 --- a/app/src/gplay/AndroidManifest.xml +++ b/app/src/gplay/AndroidManifest.xml @@ -2,6 +2,8 @@ ~ Nextcloud Talk application ~ ~ @author Mario Danic + ~ @author Tim Krüger + ~ Copyright (C) 2022 Tim Krüger ~ Copyright (C) 2017-2019 Mario Danic ~ ~ This program is free software: you can redistribute it and/or modify @@ -38,7 +40,7 @@ diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt similarity index 97% rename from app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt rename to app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt index 86cd47dde..57fcec7b7 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt @@ -81,7 +81,7 @@ import javax.inject.Inject @SuppressLint("LongLogTag") @AutoInjector(NextcloudTalkApplication::class) -class MagicFirebaseMessagingService : FirebaseMessagingService() { +class ChatAndCallMessagingService : FirebaseMessagingService() { @JvmField @Inject var appPreferences: AppPreferences? = null @@ -215,7 +215,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK val fullScreenPendingIntent = PendingIntent.getActivity( - this@MagicFirebaseMessagingService, + this@ChatAndCallMessagingService, 0, fullScreenIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -231,7 +231,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { val baseUrl = uri.host val notification = - NotificationCompat.Builder(this@MagicFirebaseMessagingService, notificationChannelId) + NotificationCompat.Builder(this@ChatAndCallMessagingService, notificationChannelId) .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_CALL) .setSmallIcon(R.drawable.ic_call_black_24dp) @@ -333,7 +333,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() { } companion object { - const val TAG = "MagicFirebaseMessagingService" + private val TAG = ChatAndCallMessagingService::class.simpleName private const val OBSERVABLE_COUNT = 12 private const val OBSERVABLE_DELAY: Long = 5 } From 58c40e6ee244e6b78cc8754e8cc621e8400507d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 23 Jun 2022 10:59:12 +0200 Subject: [PATCH 13/13] Remove annotation '@JvmField' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the '@JvmField' with the Kotlin way 'lateinit'. Signed-off-by: Tim Krüger --- .../firebase/ChatAndCallMessagingService.kt | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt index 57fcec7b7..e9eadfbeb 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/ChatAndCallMessagingService.kt @@ -2,6 +2,8 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2017-2019 Mario Danic * * This program is free software: you can redistribute it and/or modify @@ -82,31 +84,28 @@ import javax.inject.Inject @SuppressLint("LongLogTag") @AutoInjector(NextcloudTalkApplication::class) class ChatAndCallMessagingService : FirebaseMessagingService() { - @JvmField - @Inject - var appPreferences: AppPreferences? = null - var isServiceInForeground: Boolean = false + @Inject + lateinit var appPreferences: AppPreferences + + private var isServiceInForeground: Boolean = false private var decryptedPushMessage: DecryptedPushMessage? = null private var signatureVerification: SignatureVerification? = null private var handler: Handler = Handler() - @JvmField @Inject - var retrofit: Retrofit? = null + lateinit var retrofit: Retrofit - @JvmField @Inject - var okHttpClient: OkHttpClient? = null + lateinit var okHttpClient: OkHttpClient - @JvmField @Inject - var eventBus: EventBus? = null + lateinit var eventBus: EventBus override fun onCreate() { super.onCreate() sharedApplication!!.componentApplication.inject(this) - eventBus?.register(this) + eventBus.register(this) } @Subscribe(threadMode = ThreadMode.BACKGROUND) @@ -119,7 +118,7 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { override fun onDestroy() { Log.d(TAG, "onDestroy") isServiceInForeground = false - eventBus?.unregister(this) + eventBus.unregister(this) stopForeground(true) handler.removeCallbacksAndMessages(null) super.onDestroy() @@ -128,7 +127,7 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) sharedApplication!!.componentApplication.inject(this) - appPreferences!!.pushToken = token + appPreferences.pushToken = token Log.d(TAG, "onNewToken. token = $token") val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "onNewToken").build() @@ -225,7 +224,7 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { } ) - val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences!!) + val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences) val notificationChannelId = NotificationUtils.NOTIFICATION_CHANNEL_CALLS_V4 val uri = Uri.parse(signatureVerification!!.userEntity!!.baseUrl) val baseUrl = uri.host @@ -268,8 +267,8 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { decryptedPushMessage: DecryptedPushMessage ) { Log.d(TAG, "checkIfCallIsActive") - val ncApi = retrofit!!.newBuilder() - .client(okHttpClient!!.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build() + val ncApi = retrofit.newBuilder() + .client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build() .create(NcApi::class.java) var hasParticipantsInCall = true var inCallOnDifferentDevice = false @@ -297,9 +296,7 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { } .subscribeOn(Schedulers.io()) .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } + override fun onSubscribe(d: Disposable) = Unit override fun onNext(participantsOverall: ParticipantsOverall) { val participantList: List = participantsOverall.ocs!!.data!! @@ -321,9 +318,7 @@ class ChatAndCallMessagingService : FirebaseMessagingService() { } } - override fun onError(e: Throwable) { - // unused atm - } + override fun onError(e: Throwable) = Unit override fun onComplete() { stopForeground(true)