mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 20:19:42 +01:00
Merge pull request #2139 from nextcloud/bugfix/2132/android-build-dependend-bluetooth-permissions
Android build dependend bluetooth permissions
This commit is contained in:
commit
8b9a204c40
@ -39,7 +39,7 @@ android {
|
|||||||
buildToolsVersion '33.0.0'
|
buildToolsVersion '33.0.0'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
|
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
~ Nextcloud Talk application
|
~ Nextcloud Talk application
|
||||||
~
|
~
|
||||||
~ @author Mario Danic
|
~ @author Mario Danic
|
||||||
|
~ @author Tim Krüger
|
||||||
|
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||||
~
|
~
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
@ -38,8 +40,9 @@
|
|||||||
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.firebase.MagicFirebaseMessagingService"
|
android:name=".services.firebase.ChatAndCallMessagingService"
|
||||||
android:exported="false">
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="phoneCall">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Tim Krüger
|
||||||
|
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -24,6 +26,7 @@ import android.app.Notification
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
@ -80,32 +83,29 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@SuppressLint("LongLogTag")
|
@SuppressLint("LongLogTag")
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
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 decryptedPushMessage: DecryptedPushMessage? = null
|
||||||
private var signatureVerification: SignatureVerification? = null
|
private var signatureVerification: SignatureVerification? = null
|
||||||
private var handler: Handler = Handler()
|
private var handler: Handler = Handler()
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Inject
|
@Inject
|
||||||
var retrofit: Retrofit? = null
|
lateinit var retrofit: Retrofit
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Inject
|
@Inject
|
||||||
var okHttpClient: OkHttpClient? = null
|
lateinit var okHttpClient: OkHttpClient
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Inject
|
@Inject
|
||||||
var eventBus: EventBus? = null
|
lateinit var eventBus: EventBus
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
eventBus?.register(this)
|
eventBus.register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||||
@ -118,7 +118,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "onDestroy")
|
Log.d(TAG, "onDestroy")
|
||||||
isServiceInForeground = false
|
isServiceInForeground = false
|
||||||
eventBus?.unregister(this)
|
eventBus.unregister(this)
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
handler.removeCallbacksAndMessages(null)
|
handler.removeCallbacksAndMessages(null)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@ -127,7 +127,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
override fun onNewToken(token: String) {
|
override fun onNewToken(token: String) {
|
||||||
super.onNewToken(token)
|
super.onNewToken(token)
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
appPreferences!!.pushToken = token
|
appPreferences.pushToken = token
|
||||||
Log.d(TAG, "onNewToken. token = $token")
|
Log.d(TAG, "onNewToken. token = $token")
|
||||||
|
|
||||||
val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "onNewToken").build()
|
val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "onNewToken").build()
|
||||||
@ -168,7 +168,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
Log.d(NotificationWorker.TAG, "Invalid private key " + e1.localizedMessage)
|
Log.d(NotificationWorker.TAG, "Invalid private key " + e1.localizedMessage)
|
||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.d(NotificationWorker.TAG, "Something went very wrong " + exception.localizedMessage)
|
Log.d(NotificationWorker.TAG, "Something went very wrong " + exception.localizedMessage, exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,19 +214,23 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
|
|
||||||
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
val fullScreenPendingIntent = PendingIntent.getActivity(
|
val fullScreenPendingIntent = PendingIntent.getActivity(
|
||||||
this@MagicFirebaseMessagingService,
|
this@ChatAndCallMessagingService,
|
||||||
0,
|
0,
|
||||||
fullScreenIntent,
|
fullScreenIntent,
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
} else {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences!!)
|
val soundUri = getCallRingtoneUri(applicationContext!!, appPreferences)
|
||||||
val notificationChannelId = NotificationUtils.NOTIFICATION_CHANNEL_CALLS_V4
|
val notificationChannelId = NotificationUtils.NOTIFICATION_CHANNEL_CALLS_V4
|
||||||
val uri = Uri.parse(signatureVerification!!.userEntity!!.baseUrl)
|
val uri = Uri.parse(signatureVerification!!.userEntity!!.baseUrl)
|
||||||
val baseUrl = uri.host
|
val baseUrl = uri.host
|
||||||
|
|
||||||
val notification =
|
val notification =
|
||||||
NotificationCompat.Builder(this@MagicFirebaseMessagingService, notificationChannelId)
|
NotificationCompat.Builder(this@ChatAndCallMessagingService, notificationChannelId)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||||
.setSmallIcon(R.drawable.ic_call_black_24dp)
|
.setSmallIcon(R.drawable.ic_call_black_24dp)
|
||||||
@ -263,8 +267,8 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
decryptedPushMessage: DecryptedPushMessage
|
decryptedPushMessage: DecryptedPushMessage
|
||||||
) {
|
) {
|
||||||
Log.d(TAG, "checkIfCallIsActive")
|
Log.d(TAG, "checkIfCallIsActive")
|
||||||
val ncApi = retrofit!!.newBuilder()
|
val ncApi = retrofit.newBuilder()
|
||||||
.client(okHttpClient!!.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build()
|
.client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build()
|
||||||
.create(NcApi::class.java)
|
.create(NcApi::class.java)
|
||||||
var hasParticipantsInCall = true
|
var hasParticipantsInCall = true
|
||||||
var inCallOnDifferentDevice = false
|
var inCallOnDifferentDevice = false
|
||||||
@ -292,9 +296,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe(object : Observer<ParticipantsOverall> {
|
.subscribe(object : Observer<ParticipantsOverall> {
|
||||||
override fun onSubscribe(d: Disposable) {
|
override fun onSubscribe(d: Disposable) = Unit
|
||||||
// unused atm
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||||
val participantList: List<Participant> = participantsOverall.ocs!!.data!!
|
val participantList: List<Participant> = participantsOverall.ocs!!.data!!
|
||||||
@ -316,9 +318,8 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
override fun onError(e: Throwable) = Unit
|
||||||
// unused atm
|
|
||||||
}
|
|
||||||
override fun onComplete() {
|
override fun onComplete() {
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
handler.removeCallbacksAndMessages(null)
|
handler.removeCallbacksAndMessages(null)
|
||||||
@ -327,7 +328,7 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MagicFirebaseMessagingService"
|
private val TAG = ChatAndCallMessagingService::class.simpleName
|
||||||
private const val OBSERVABLE_COUNT = 12
|
private const val OBSERVABLE_COUNT = 12
|
||||||
private const val OBSERVABLE_DELAY: Long = 5
|
private const val OBSERVABLE_DELAY: Long = 5
|
||||||
}
|
}
|
@ -37,8 +37,11 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
|
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
|
||||||
android:maxSdkVersion="22" />
|
android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission
|
||||||
|
android:name="android.permission.BLUETOOTH"
|
||||||
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.GET_ACCOUNTS"
|
android:name="android.permission.GET_ACCOUNTS"
|
||||||
@ -105,6 +108,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:label="@string/nc_app_name"
|
android:label="@string/nc_app_name"
|
||||||
|
android:exported="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -127,6 +131,8 @@
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" />
|
<data android:mimeType="vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:scheme="file" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -185,7 +191,8 @@
|
|||||||
android:name=".messagesearch.MessageSearchActivity"
|
android:name=".messagesearch.MessageSearchActivity"
|
||||||
android:theme="@style/AppTheme" />
|
android:theme="@style/AppTheme" />
|
||||||
|
|
||||||
<receiver android:name=".receivers.PackageReplacedReceiver">
|
<receiver android:name=".receivers.PackageReplacedReceiver"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -208,7 +215,8 @@
|
|||||||
android:resource="@xml/contacts" />
|
android:resource="@xml/contacts" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".utils.AuthenticatorService">
|
<service android:name=".utils.AuthenticatorService"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
<action android:name="android.accounts.AccountAuthenticator" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -30,6 +30,7 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
@ -94,7 +95,7 @@ import com.nextcloud.talk.utils.database.user.UserUtils;
|
|||||||
import com.nextcloud.talk.utils.power.PowerManagerUtils;
|
import com.nextcloud.talk.utils.power.PowerManagerUtils;
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
|
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.MagicWebRTCUtils;
|
||||||
import com.nextcloud.talk.webrtc.MagicWebSocketInstance;
|
import com.nextcloud.talk.webrtc.MagicWebSocketInstance;
|
||||||
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
|
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
|
||||||
@ -139,12 +140,15 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import autodagger.AutoInjector;
|
import autodagger.AutoInjector;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
@ -158,6 +162,8 @@ import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
|
|||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
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.JOB_ID;
|
||||||
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
|
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
|
||||||
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
|
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
|
||||||
@ -183,11 +189,11 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
public static final String TAG = "CallActivity";
|
public static final String TAG = "CallActivity";
|
||||||
|
|
||||||
public MagicAudioManager audioManager;
|
public WebRtcAudioManger audioManager;
|
||||||
|
|
||||||
private static final String[] PERMISSIONS_CALL = {
|
private static final String[] PERMISSIONS_CALL = {
|
||||||
android.Manifest.permission.CAMERA,
|
Manifest.permission.CAMERA,
|
||||||
android.Manifest.permission.RECORD_AUDIO,
|
Manifest.permission.RECORD_AUDIO
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] PERMISSIONS_CAMERA = {
|
private static final String[] PERMISSIONS_CAMERA = {
|
||||||
@ -269,6 +275,13 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
private AudioOutputDialog audioOutputDialog;
|
private AudioOutputDialog audioOutputDialog;
|
||||||
|
|
||||||
|
private final ActivityResultLauncher<String> requestBluetoothPermissionLauncher =
|
||||||
|
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
|
||||||
|
if (isGranted) {
|
||||||
|
enableBluetoothManager();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@ -318,6 +331,9 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
.setRepeatCount(PulseAnimation.INFINITE)
|
.setRepeatCount(PulseAnimation.INFINITE)
|
||||||
.setRepeatMode(PulseAnimation.REVERSE);
|
.setRepeatMode(PulseAnimation.REVERSE);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
requestBluetoothPermission();
|
||||||
|
}
|
||||||
basicInitialization();
|
basicInitialization();
|
||||||
participantDisplayItems = new HashMap<>();
|
participantDisplayItems = new HashMap<>();
|
||||||
initViews();
|
initViews();
|
||||||
@ -327,6 +343,22 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
updateSelfVideoViewPosition();
|
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
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@ -420,16 +452,16 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
// Create and audio manager that will take care of audio routing,
|
// Create and audio manager that will take care of audio routing,
|
||||||
// audio modes, audio device enumeration etc.
|
// 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
|
// Store existing audio settings and change audio mode to
|
||||||
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
||||||
Log.d(TAG, "Starting the audio manager...");
|
Log.d(TAG, "Starting the audio manager...");
|
||||||
audioManager.start(this::onAudioManagerDevicesChanged);
|
audioManager.start(this::onAudioManagerDevicesChanged);
|
||||||
|
|
||||||
if (isVoiceOnlyCall) {
|
if (isVoiceOnlyCall) {
|
||||||
setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE);
|
setAudioOutputChannel(WebRtcAudioManger.AudioDevice.EARPIECE);
|
||||||
} else {
|
} else {
|
||||||
setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
|
setAudioOutputChannel(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
iceServers = new ArrayList<>();
|
iceServers = new ArrayList<>();
|
||||||
@ -463,14 +495,14 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
microphoneInitialization();
|
microphoneInitialization();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioOutputChannel(MagicAudioManager.AudioDevice selectedAudioDevice) {
|
public void setAudioOutputChannel(WebRtcAudioManger.AudioDevice selectedAudioDevice) {
|
||||||
if (audioManager != null) {
|
if (audioManager != null) {
|
||||||
audioManager.selectAudioDevice(selectedAudioDevice);
|
audioManager.selectAudioDevice(selectedAudioDevice);
|
||||||
updateAudioOutputButton(audioManager.getCurrentAudioDevice());
|
updateAudioOutputButton(audioManager.getCurrentAudioDevice());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAudioOutputButton(MagicAudioManager.AudioDevice activeAudioDevice) {
|
private void updateAudioOutputButton(WebRtcAudioManger.AudioDevice activeAudioDevice) {
|
||||||
switch (activeAudioDevice) {
|
switch (activeAudioDevice) {
|
||||||
case BLUETOOTH:
|
case BLUETOOTH:
|
||||||
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
||||||
@ -764,14 +796,14 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onAudioManagerDevicesChanged(
|
private void onAudioManagerDevicesChanged(
|
||||||
final MagicAudioManager.AudioDevice currentDevice,
|
final WebRtcAudioManger.AudioDevice currentDevice,
|
||||||
final Set<MagicAudioManager.AudioDevice> availableDevices) {
|
final Set<WebRtcAudioManger.AudioDevice> availableDevices) {
|
||||||
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
|
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
|
||||||
+ "currentDevice: " + currentDevice);
|
+ "currentDevice: " + currentDevice);
|
||||||
|
|
||||||
final boolean shouldDisableProximityLock = (currentDevice.equals(MagicAudioManager.AudioDevice.WIRED_HEADSET)
|
final boolean shouldDisableProximityLock = (currentDevice.equals(WebRtcAudioManger.AudioDevice.WIRED_HEADSET)
|
||||||
|| currentDevice.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
|
|| currentDevice.equals(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE)
|
||||||
|| currentDevice.equals(MagicAudioManager.AudioDevice.BLUETOOTH));
|
|| currentDevice.equals(WebRtcAudioManger.AudioDevice.BLUETOOTH));
|
||||||
|
|
||||||
if (shouldDisableProximityLock) {
|
if (shouldDisableProximityLock) {
|
||||||
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
|
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
|
||||||
@ -2579,12 +2611,19 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
final ArrayList<RemoteAction> actions = new ArrayList<>();
|
final ArrayList<RemoteAction> actions = new ArrayList<>();
|
||||||
|
|
||||||
final Icon icon = Icon.createWithResource(this, iconId);
|
final Icon icon = Icon.createWithResource(this, iconId);
|
||||||
|
|
||||||
|
int intentFlag;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlag = FLAG_MUTABLE;
|
||||||
|
} else {
|
||||||
|
intentFlag = 0;
|
||||||
|
}
|
||||||
final PendingIntent intent =
|
final PendingIntent intent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
requestCode,
|
requestCode,
|
||||||
new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode),
|
new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode),
|
||||||
0);
|
intentFlag);
|
||||||
|
|
||||||
actions.add(new RemoteAction(icon, title, title, intent));
|
actions.add(new RemoteAction(icon, title, title, intent));
|
||||||
|
|
||||||
|
@ -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
|
// 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
|
// See https://github.com/nextcloud/talk-android/issues/2111
|
||||||
int requestCode = (int) System.currentTimeMillis();
|
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());
|
Uri uri = Uri.parse(signatureVerification.getUserEntity().getBaseUrl());
|
||||||
String baseUrl = uri.getHost();
|
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.
|
// 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_SYSTEM_NOTIFICATION_ID(), systemNotificationId);
|
||||||
actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId());
|
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 replyPendingIntent =
|
||||||
PendingIntent.getBroadcast(context, systemNotificationId, actualIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.getBroadcast(context, systemNotificationId, actualIntent, intentFlag);
|
||||||
|
|
||||||
NotificationCompat.Action replyAction =
|
NotificationCompat.Action replyAction =
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_reply, replyLabel, replyPendingIntent)
|
new NotificationCompat.Action.Builder(R.drawable.ic_reply, replyLabel, replyPendingIntent)
|
||||||
|
@ -30,7 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
|||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.activities.CallActivity
|
import com.nextcloud.talk.activities.CallActivity
|
||||||
import com.nextcloud.talk.databinding.DialogAudioOutputBinding
|
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) {
|
class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(callActivity) {
|
||||||
|
|
||||||
@ -47,26 +47,26 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateOutputDeviceList() {
|
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
|
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.VISIBLE
|
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
|
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.VISIBLE
|
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
|
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.VISIBLE
|
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callActivity.audioManager?.currentAudioDevice?.equals(
|
if (callActivity.audioManager?.currentAudioDevice?.equals(
|
||||||
MagicAudioManager.AudioDevice.WIRED_HEADSET
|
WebRtcAudioManger.AudioDevice.WIRED_HEADSET
|
||||||
) == true
|
) == true
|
||||||
) {
|
) {
|
||||||
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
|
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
|
||||||
@ -81,7 +81,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
|
|
||||||
private fun highlightActiveOutputChannel() {
|
private fun highlightActiveOutputChannel() {
|
||||||
when (callActivity.audioManager?.currentAudioDevice) {
|
when (callActivity.audioManager?.currentAudioDevice) {
|
||||||
MagicAudioManager.AudioDevice.BLUETOOTH -> {
|
WebRtcAudioManger.AudioDevice.BLUETOOTH -> {
|
||||||
dialogAudioOutputBinding.audioOutputBluetoothIcon.setColorFilter(
|
dialogAudioOutputBinding.audioOutputBluetoothIcon.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
context,
|
context,
|
||||||
@ -94,7 +94,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MagicAudioManager.AudioDevice.SPEAKER_PHONE -> {
|
WebRtcAudioManger.AudioDevice.SPEAKER_PHONE -> {
|
||||||
dialogAudioOutputBinding.audioOutputSpeakerIcon.setColorFilter(
|
dialogAudioOutputBinding.audioOutputSpeakerIcon.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
context,
|
context,
|
||||||
@ -107,7 +107,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MagicAudioManager.AudioDevice.EARPIECE -> {
|
WebRtcAudioManger.AudioDevice.EARPIECE -> {
|
||||||
dialogAudioOutputBinding.audioOutputEarspeakerIcon.setColorFilter(
|
dialogAudioOutputBinding.audioOutputEarspeakerIcon.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
context,
|
context,
|
||||||
@ -120,7 +120,7 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MagicAudioManager.AudioDevice.WIRED_HEADSET -> {
|
WebRtcAudioManger.AudioDevice.WIRED_HEADSET -> {
|
||||||
dialogAudioOutputBinding.audioOutputWiredHeadsetIcon.setColorFilter(
|
dialogAudioOutputBinding.audioOutputWiredHeadsetIcon.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
context,
|
context,
|
||||||
@ -139,17 +139,17 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
|
|||||||
|
|
||||||
private fun initClickListeners() {
|
private fun initClickListeners() {
|
||||||
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
|
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
|
||||||
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.BLUETOOTH)
|
callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.BLUETOOTH)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
|
dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
|
||||||
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
|
callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.SPEAKER_PHONE)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
|
dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
|
||||||
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE)
|
callActivity.setAudioOutputChannel(WebRtcAudioManger.AudioDevice.EARPIECE)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Tim Krüger
|
||||||
|
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
public class WebRtcAudioManger {
|
||||||
* MagicAudioManager manages all audio related parts of the AppRTC demo.
|
private static final String TAG = WebRtcAudioManger.class.getCanonicalName();
|
||||||
*/
|
|
||||||
public class MagicAudioManager {
|
|
||||||
private static final String TAG = "MagicAudioManager";
|
|
||||||
private final Context magicContext;
|
private final Context magicContext;
|
||||||
private final MagicBluetoothManager bluetoothManager;
|
private final WebRtcBluetoothManager bluetoothManager;
|
||||||
private boolean useProximitySensor;
|
private final boolean useProximitySensor;
|
||||||
private AudioManager audioManager;
|
private final AudioManager audioManager;
|
||||||
private AudioManagerListener audioManagerListener;
|
private AudioManagerListener audioManagerListener;
|
||||||
private AudioManagerState amState;
|
private AudioManagerState amState;
|
||||||
private int savedAudioMode = AudioManager.MODE_INVALID;
|
private int savedAudioMode = AudioManager.MODE_INVALID;
|
||||||
@ -75,17 +74,17 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
private Set<AudioDevice> audioDevices = new HashSet<>();
|
private Set<AudioDevice> audioDevices = new HashSet<>();
|
||||||
|
|
||||||
private BroadcastReceiver wiredHeadsetReceiver;
|
private final BroadcastReceiver wiredHeadsetReceiver;
|
||||||
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
|
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");
|
Log.d(TAG, "ctor");
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
magicContext = context;
|
magicContext = context;
|
||||||
audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
|
audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
|
||||||
bluetoothManager = MagicBluetoothManager.create(context, this);
|
bluetoothManager = WebRtcBluetoothManager.create(context, this);
|
||||||
wiredHeadsetReceiver = new WiredHeadsetReceiver();
|
wiredHeadsetReceiver = new WiredHeadsetReceiver();
|
||||||
amState = AudioManagerState.UNINITIALIZED;
|
amState = AudioManagerState.UNINITIALIZED;
|
||||||
|
|
||||||
@ -111,8 +110,14 @@ public class MagicAudioManager {
|
|||||||
/**
|
/**
|
||||||
* Construction.
|
* Construction.
|
||||||
*/
|
*/
|
||||||
public static MagicAudioManager create(Context context, boolean useProximitySensor) {
|
public static WebRtcAudioManger create(Context context, boolean useProximitySensor) {
|
||||||
return new MagicAudioManager(context, 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));
|
.SENSOR_NEAR, null, null, null, null));
|
||||||
|
|
||||||
} else {
|
} 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");
|
Log.d(TAG, "switched to SPEAKER_PHONE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=far");
|
||||||
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
||||||
@ -228,9 +233,7 @@ public class MagicAudioManager {
|
|||||||
currentAudioDevice = AudioDevice.NONE;
|
currentAudioDevice = AudioDevice.NONE;
|
||||||
audioDevices.clear();
|
audioDevices.clear();
|
||||||
|
|
||||||
// Initialize and start Bluetooth if a BT device is available or initiate
|
startBluetoothManager();
|
||||||
// detection of new (enabled) BT devices.
|
|
||||||
bluetoothManager.start();
|
|
||||||
|
|
||||||
// Do initial selection of audio device. This setting can later be changed
|
// 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
|
// either by adding/removing a BT or wired headset or by covering/uncovering
|
||||||
@ -256,7 +259,9 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
unregisterReceiver(wiredHeadsetReceiver);
|
unregisterReceiver(wiredHeadsetReceiver);
|
||||||
|
|
||||||
|
if(bluetoothManager.started()) {
|
||||||
bluetoothManager.stop();
|
bluetoothManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
// Restore previously stored audio states.
|
// Restore previously stored audio states.
|
||||||
setSpeakerphoneOn(savedIsSpeakerPhoneOn);
|
setSpeakerphoneOn(savedIsSpeakerPhoneOn);
|
||||||
@ -411,17 +416,17 @@ public class MagicAudioManager {
|
|||||||
+ "current=" + currentAudioDevice + ", "
|
+ "current=" + currentAudioDevice + ", "
|
||||||
+ "user selected=" + userSelectedAudioDevice);
|
+ "user selected=" + userSelectedAudioDevice);
|
||||||
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
if (bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_UNAVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_DISCONNECTING) {
|
||||||
bluetoothManager.updateDevice();
|
bluetoothManager.updateDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<AudioDevice> newAudioDevices = new HashSet<>();
|
Set<AudioDevice> newAudioDevices = new HashSet<>();
|
||||||
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
|
if (bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE) {
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE) {
|
||||||
newAudioDevices.add(AudioDevice.BLUETOOTH);
|
newAudioDevices.add(AudioDevice.BLUETOOTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,7 +446,7 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
// Correct user selected audio devices if needed.
|
// Correct user selected audio devices if needed.
|
||||||
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH
|
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH
|
||||||
&& bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE) {
|
&& bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_UNAVAILABLE) {
|
||||||
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
||||||
}
|
}
|
||||||
if (userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE && hasWiredHeadset) {
|
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
|
// Need to start Bluetooth if it is available and user either selected it explicitly or
|
||||||
// user did not select any output device.
|
// user did not select any output device.
|
||||||
boolean needBluetoothAudioStart =
|
boolean needBluetoothAudioStart =
|
||||||
bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
|
||||||
&& (userSelectedAudioDevice == AudioDevice.NONE
|
&& (userSelectedAudioDevice == AudioDevice.NONE
|
||||||
|| userSelectedAudioDevice == AudioDevice.BLUETOOTH);
|
|| userSelectedAudioDevice == AudioDevice.BLUETOOTH);
|
||||||
|
|
||||||
// Need to stop Bluetooth audio if user selected different device and
|
// Need to stop Bluetooth audio if user selected different device and
|
||||||
// Bluetooth SCO connection is established or in the process.
|
// Bluetooth SCO connection is established or in the process.
|
||||||
boolean needBluetoothAudioStop =
|
boolean needBluetoothAudioStop =
|
||||||
(bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
|
(bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING)
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING)
|
||||||
&& (userSelectedAudioDevice != AudioDevice.NONE
|
&& (userSelectedAudioDevice != AudioDevice.NONE
|
||||||
&& userSelectedAudioDevice != AudioDevice.BLUETOOTH);
|
&& userSelectedAudioDevice != AudioDevice.BLUETOOTH);
|
||||||
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
if (bluetoothManager.getState() == WebRtcBluetoothManager.State.HEADSET_AVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTING
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTING
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
|| bluetoothManager.getState() == WebRtcBluetoothManager.State.SCO_CONNECTED) {
|
||||||
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
|
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
|
||||||
+ "stop=" + needBluetoothAudioStop + ", "
|
+ "stop=" + needBluetoothAudioStop + ", "
|
||||||
+ "BT state=" + bluetoothManager.getState());
|
+ "BT state=" + bluetoothManager.getState());
|
||||||
@ -494,7 +499,7 @@ public class MagicAudioManager {
|
|||||||
// Update selected audio device.
|
// Update selected audio device.
|
||||||
AudioDevice newCurrentAudioDevice;
|
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
|
// 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;
|
// device. Note that it is not sufficient that a headset is available;
|
||||||
// an active SCO channel must also be up and running.
|
// an active SCO channel must also be up and running.
|
@ -2,6 +2,8 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Tim Krüger
|
||||||
|
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -31,6 +33,7 @@
|
|||||||
|
|
||||||
package com.nextcloud.talk.webrtc;
|
package com.nextcloud.talk.webrtc;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
@ -47,20 +50,23 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.webrtc.ThreadUtils;
|
import org.webrtc.ThreadUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MagicBluetoothManager {
|
import androidx.core.app.ActivityCompat;
|
||||||
private static final String TAG = "MagicBluetoothManager";
|
|
||||||
|
public class WebRtcBluetoothManager {
|
||||||
|
private static final String TAG = WebRtcBluetoothManager.class.getCanonicalName();
|
||||||
|
|
||||||
// Timeout interval for starting or stopping audio to a Bluetooth SCO device.
|
// Timeout interval for starting or stopping audio to a Bluetooth SCO device.
|
||||||
private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
|
private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
|
||||||
// Maximum number of SCO connection attempts.
|
// Maximum number of SCO connection attempts.
|
||||||
private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
|
private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
|
||||||
private final Context apprtcContext;
|
private final Context apprtcContext;
|
||||||
private final MagicAudioManager apprtcAudioManager;
|
private final WebRtcAudioManger webRtcAudioManager;
|
||||||
private final AudioManager audioManager;
|
private final AudioManager audioManager;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final BluetoothProfile.ServiceListener bluetoothServiceListener;
|
private final BluetoothProfile.ServiceListener bluetoothServiceListener;
|
||||||
@ -73,18 +79,14 @@ public class MagicBluetoothManager {
|
|||||||
// Runs when the Bluetooth timeout expires. We use that timeout after calling
|
// Runs when the Bluetooth timeout expires. We use that timeout after calling
|
||||||
// startScoAudio() or stopScoAudio() because we're not guaranteed to get a
|
// startScoAudio() or stopScoAudio() because we're not guaranteed to get a
|
||||||
// callback after those calls.
|
// callback after those calls.
|
||||||
private final Runnable bluetoothTimeoutRunnable = new Runnable() {
|
private final Runnable bluetoothTimeoutRunnable = this::bluetoothTimeout;
|
||||||
@Override
|
private boolean started = false;
|
||||||
public void run() {
|
|
||||||
bluetoothTimeout();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected MagicBluetoothManager(Context context, MagicAudioManager audioManager) {
|
protected WebRtcBluetoothManager(Context context, WebRtcAudioManger audioManager) {
|
||||||
Log.d(TAG, "ctor");
|
Log.d(TAG, "ctor");
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
apprtcContext = context;
|
apprtcContext = context;
|
||||||
apprtcAudioManager = audioManager;
|
webRtcAudioManager = audioManager;
|
||||||
this.audioManager = getAudioManager(context);
|
this.audioManager = getAudioManager(context);
|
||||||
bluetoothState = State.UNINITIALIZED;
|
bluetoothState = State.UNINITIALIZED;
|
||||||
bluetoothServiceListener = new BluetoothServiceListener();
|
bluetoothServiceListener = new BluetoothServiceListener();
|
||||||
@ -95,8 +97,8 @@ public class MagicBluetoothManager {
|
|||||||
/**
|
/**
|
||||||
* Construction.
|
* Construction.
|
||||||
*/
|
*/
|
||||||
static MagicBluetoothManager create(Context context, MagicAudioManager audioManager) {
|
static WebRtcBluetoothManager create(Context context, WebRtcAudioManger audioManager) {
|
||||||
return new MagicBluetoothManager(context, audioManager);
|
return new WebRtcBluetoothManager(context, audioManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,11 +124,11 @@ public class MagicBluetoothManager {
|
|||||||
* Note that the MagicAudioManager is also involved in driving this state
|
* Note that the MagicAudioManager is also involved in driving this state
|
||||||
* change.
|
* change.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
public void start() {
|
public void start() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "start");
|
Log.d(TAG, "start");
|
||||||
if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) {
|
if(hasNoBluetoothPermission()){
|
||||||
Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (bluetoothState != State.UNINITIALIZED) {
|
if (bluetoothState != State.UNINITIALIZED) {
|
||||||
@ -166,6 +168,7 @@ public class MagicBluetoothManager {
|
|||||||
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
|
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
|
||||||
Log.d(TAG, "Bluetooth proxy for headset profile has started");
|
Log.d(TAG, "Bluetooth proxy for headset profile has started");
|
||||||
bluetoothState = State.HEADSET_UNAVAILABLE;
|
bluetoothState = State.HEADSET_UNAVAILABLE;
|
||||||
|
started = true;
|
||||||
Log.d(TAG, "start done: BT state=" + bluetoothState);
|
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
|
* HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected
|
||||||
* device if available.
|
* device if available.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
public void updateDevice() {
|
public void updateDevice() {
|
||||||
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
|
boolean hasNoBluetoothPermissions = hasNoBluetoothPermission();
|
||||||
|
if (hasNoBluetoothPermissions ||
|
||||||
|
bluetoothState == State.UNINITIALIZED ||
|
||||||
|
bluetoothHeadset == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "updateDevice");
|
Log.d(TAG, "updateDevice");
|
||||||
@ -307,16 +314,28 @@ public class MagicBluetoothManager {
|
|||||||
return bluetoothAdapter.getProfileProxy(context, listener, profile);
|
return bluetoothAdapter.getProfileProxy(context, listener, profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasPermission(Context context, String permission) {
|
private boolean hasNoBluetoothPermission() {
|
||||||
return apprtcContext.checkPermission(permission, Process.myPid(), Process.myUid())
|
String permission;
|
||||||
== PackageManager.PERMISSION_GRANTED;
|
|
||||||
|
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.
|
* Logs the state of the local Bluetooth adapter.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint({"HardwareIds", "MissingPermission"})
|
||||||
protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
|
private void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
|
||||||
Log.d(TAG, "BluetoothAdapter: "
|
Log.d(TAG, "BluetoothAdapter: "
|
||||||
+ "enabled=" + localAdapter.isEnabled() + ", "
|
+ "enabled=" + localAdapter.isEnabled() + ", "
|
||||||
+ "state=" + stateToString(localAdapter.getState()) + ", "
|
+ "state=" + stateToString(localAdapter.getState()) + ", "
|
||||||
@ -337,7 +356,7 @@ public class MagicBluetoothManager {
|
|||||||
private void updateAudioDeviceState() {
|
private void updateAudioDeviceState() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "updateAudioDeviceState");
|
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
|
* 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.
|
* happens when the BT device has been turned on during an ongoing call.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private void bluetoothTimeout() {
|
private void bluetoothTimeout() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
|
boolean hasNoBluetoothPermissions = hasNoBluetoothPermission();
|
||||||
|
if (hasNoBluetoothPermissions ||
|
||||||
|
bluetoothState == State.UNINITIALIZED ||
|
||||||
|
bluetoothHeadset == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
|
||||||
@ -435,6 +458,10 @@ public class MagicBluetoothManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean started() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
// Bluetooth connection state.
|
// Bluetooth connection state.
|
||||||
public enum State {
|
public enum State {
|
||||||
// Bluetooth is not available; no adapter or Bluetooth is off.
|
// Bluetooth is not available; no adapter or Bluetooth is off.
|
@ -1,2 +1,2 @@
|
|||||||
DO NOT TOUCH; GENERATED BY DRONE
|
DO NOT TOUCH; GENERATED BY DRONE
|
||||||
<span class="mdl-layout-title">Lint Report: 2 errors and 123 warnings</span>
|
<span class="mdl-layout-title">Lint Report: 1 error and 116 warnings</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user