diff --git a/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt
new file mode 100644
index 000000000..0f2ee06fd
--- /dev/null
+++ b/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.compatibility
+
+import android.Manifest
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+class Api33Compatibility {
+ companion object {
+ private const val TAG = "[API 33 Compatibility]"
+
+ fun getAllRequiredPermissionsArray(): Array {
+ return arrayOf(
+ Manifest.permission.POST_NOTIFICATIONS,
+ Manifest.permission.READ_CONTACTS,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.CAMERA
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt
new file mode 100644
index 000000000..89f6ccf27
--- /dev/null
+++ b/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.compatibility
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import org.linphone.core.tools.Log
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class Api34Compatibility {
+ companion object {
+ private const val TAG = "[API 34 Compatibility]"
+
+ fun hasFullScreenIntentPermission(context: Context): Boolean {
+ val notificationManager = context.getSystemService(NotificationManager::class.java) as NotificationManager
+ // See https://developer.android.com/reference/android/app/NotificationManager#canUseFullScreenIntent%28%29
+ val granted = notificationManager.canUseFullScreenIntent()
+ if (granted) {
+ Log.i("$TAG Full screen intent permission is granted")
+ } else {
+ Log.w("$TAG Full screen intent permission isn't granted yet!")
+ }
+ return granted
+ }
+
+ fun requestFullScreenIntentPermission(context: Context) {
+ val intent = Intent()
+ // See https://developer.android.com/reference/android/provider/Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
+ intent.action = Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
+ intent.data = Uri.parse("package:${context.packageName}")
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+ Log.i("$TAG Starting ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT")
+ ContextCompat.startActivity(context, intent, null)
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
index 83c111a05..2be436622 100644
--- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt
+++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt
@@ -19,9 +19,11 @@
*/
package org.linphone.compatibility
+import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Service
+import android.content.Context
import android.net.Uri
import android.view.View
import org.linphone.mediastream.Version
@@ -76,5 +78,31 @@ class Compatibility {
Api28Compatibility.getMediaCollectionUri(isImage, isVideo, isAudio)
}
}
+
+ fun getAllRequiredPermissionsArray(): Array {
+ if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) {
+ return Api33Compatibility.getAllRequiredPermissionsArray()
+ }
+ return arrayOf(
+ Manifest.permission.READ_CONTACTS,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.CAMERA
+ )
+ }
+
+ fun hasFullScreenIntentPermission(context: Context): Boolean {
+ if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) {
+ return Api34Compatibility.hasFullScreenIntentPermission(context)
+ }
+ return true
+ }
+
+ fun requestFullScreenIntentPermission(context: Context): Boolean {
+ if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) {
+ Api34Compatibility.requestFullScreenIntentPermission(context)
+ return true
+ }
+ return false
+ }
}
}
diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
index 60226b85a..1c33fff42 100644
--- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
+++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
@@ -779,6 +779,11 @@ class NotificationsManager @MainThread constructor(private val context: Context)
}
val style = if (isIncoming) {
+ if (!Compatibility.hasFullScreenIntentPermission(context)) {
+ Log.e(
+ "$TAG Android >= 14 & full screen intent permission wasn't granted, incoming call may not be visible!"
+ )
+ }
NotificationCompat.CallStyle.forIncomingCall(
caller,
declineIntent,
diff --git a/app/src/main/java/org/linphone/ui/assistant/AssistantActivity.kt b/app/src/main/java/org/linphone/ui/assistant/AssistantActivity.kt
index 7ed845391..fcd7f692d 100644
--- a/app/src/main/java/org/linphone/ui/assistant/AssistantActivity.kt
+++ b/app/src/main/java/org/linphone/ui/assistant/AssistantActivity.kt
@@ -19,20 +19,20 @@
*/
package org.linphone.ui.assistant
-import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
-import android.os.PersistableBundle
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.annotation.DrawableRes
import androidx.annotation.UiThread
import androidx.core.content.ContextCompat
+import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
+import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log
import org.linphone.databinding.AssistantActivityBinding
import org.linphone.ui.GenericActivity
@@ -44,13 +44,6 @@ import org.linphone.utils.slideInToastFromTopForDuration
class AssistantActivity : GenericActivity() {
companion object {
private const val TAG = "[Assistant Activity]"
-
- val PERMISSIONS = arrayOf(
- Manifest.permission.POST_NOTIFICATIONS,
- Manifest.permission.READ_CONTACTS,
- Manifest.permission.RECORD_AUDIO,
- Manifest.permission.CAMERA
- )
}
private lateinit var binding: AssistantActivityBinding
@@ -70,15 +63,13 @@ class AssistantActivity : GenericActivity() {
}
}
}
- }
- override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
- super.onPostCreate(savedInstanceState, persistentState)
-
- if (!areAllPermissionsGranted()) {
- Log.w("$TAG Not all required permissions are granted, showing Permissions fragment")
- val action = PermissionsFragmentDirections.actionGlobalPermissionsFragment()
- binding.assistantNavContainer.findNavController().navigate(action)
+ (binding.root as? ViewGroup)?.doOnPreDraw {
+ if (!areAllPermissionsGranted()) {
+ Log.w("$TAG Not all required permissions are granted, showing Permissions fragment")
+ val action = PermissionsFragmentDirections.actionGlobalPermissionsFragment()
+ binding.assistantNavContainer.findNavController().navigate(action)
+ }
}
}
@@ -103,11 +94,17 @@ class AssistantActivity : GenericActivity() {
}
private fun areAllPermissionsGranted(): Boolean {
- for (permission in PERMISSIONS) {
+ for (permission in Compatibility.getAllRequiredPermissionsArray()) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
+ Log.w("$TAG Permission [$permission] hasn't been granted yet!")
return false
}
}
- return true
+
+ val granted = Compatibility.hasFullScreenIntentPermission(this)
+ if (granted) {
+ Log.i("$TAG All permissions have been granted!")
+ }
+ return granted
}
}
diff --git a/app/src/main/java/org/linphone/ui/assistant/fragment/PermissionsFragment.kt b/app/src/main/java/org/linphone/ui/assistant/fragment/PermissionsFragment.kt
index b17a6bf0a..1136261eb 100644
--- a/app/src/main/java/org/linphone/ui/assistant/fragment/PermissionsFragment.kt
+++ b/app/src/main/java/org/linphone/ui/assistant/fragment/PermissionsFragment.kt
@@ -30,9 +30,9 @@ import androidx.annotation.UiThread
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
+import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log
import org.linphone.databinding.AssistantPermissionsFragmentBinding
-import org.linphone.ui.assistant.AssistantActivity
@UiThread
class PermissionsFragment : Fragment() {
@@ -71,11 +71,6 @@ class PermissionsFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
- if (areAllPermissionsGranted()) {
- Log.i("$TAG All permissions have been granted, skipping")
- goToLoginFragment()
- }
-
binding.setBackClickListener {
findNavController().popBackStack()
}
@@ -88,7 +83,7 @@ class PermissionsFragment : Fragment() {
binding.setGrantAllClickListener {
Log.i("$TAG Requesting all permissions")
requestPermissionLauncher.launch(
- AssistantActivity.PERMISSIONS
+ Compatibility.getAllRequiredPermissionsArray()
)
}
@@ -99,6 +94,22 @@ class PermissionsFragment : Fragment() {
) {
requestPermissionLauncher.launch(arrayOf(Manifest.permission.MANAGE_OWN_CALLS))
}
+
+ if (!Compatibility.hasFullScreenIntentPermission(requireContext())) {
+ Log.w(
+ "$TAG Android 14 or newer detected & full screen intent permission hasn't been granted!"
+ )
+ Compatibility.requestFullScreenIntentPermission(requireContext())
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ if (areAllPermissionsGranted()) {
+ Log.i("$TAG All permissions have been granted, skipping")
+ goToLoginFragment()
+ }
}
private fun goToLoginFragment() {
@@ -107,11 +118,12 @@ class PermissionsFragment : Fragment() {
}
private fun areAllPermissionsGranted(): Boolean {
- for (permission in AssistantActivity.PERMISSIONS) {
+ for (permission in Compatibility.getAllRequiredPermissionsArray()) {
if (ContextCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED) {
+ Log.w("$TAG Permission [$permission] hasn't been granted yet!")
return false
}
}
- return true
+ return Compatibility.hasFullScreenIntentPermission(requireContext())
}
}
diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt
index 9e86b644b..4529c4792 100644
--- a/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AbstractTopBarViewModel.kt
@@ -119,13 +119,18 @@ open class AbstractTopBarViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
- override fun onDefaultAccountChanged(core: Core, defaultAccount: Account) {
- Log.i(
- "$TAG Default account has changed [${defaultAccount.params.identityAddress?.asStringUriOnly()}]"
- )
-
+ override fun onDefaultAccountChanged(core: Core, defaultAccount: Account?) {
account.value?.destroy()
- account.postValue(AccountModel(defaultAccount))
+
+ if (defaultAccount == null) {
+ Log.w("$TAG Default account is now null!")
+ } else {
+ Log.i(
+ "$TAG Default account has changed [${defaultAccount.params.identityAddress?.asStringUriOnly()}]"
+ )
+
+ account.postValue(AccountModel(defaultAccount))
+ }
updateUnreadMessagesCount()
updateMissedCallsCount()
diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt
index 493691b35..8f7acdf69 100644
--- a/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/viewmodel/DrawerMenuViewModel.kt
@@ -57,22 +57,26 @@ class DrawerMenuViewModel @UiThread constructor() : ViewModel() {
private val coreListener = object : CoreListenerStub() {
@WorkerThread
- override fun onDefaultAccountChanged(core: Core, account: Account) {
- Log.i(
- "$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] has been set as default"
- )
- for (model in accounts.value.orEmpty()) {
- if (model.account != account) {
- model.isDefault.postValue(false)
+ override fun onDefaultAccountChanged(core: Core, account: Account?) {
+ if (account == null) {
+ Log.w("$TAG Default account is now null!")
+ } else {
+ Log.i(
+ "$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] has been set as default"
+ )
+ for (model in accounts.value.orEmpty()) {
+ if (model.account != account) {
+ model.isDefault.postValue(false)
+ }
}
+ defaultAccountChangedEvent.postValue(
+ Event(account.params.identityAddress?.asStringUriOnly() ?: "")
+ )
}
- defaultAccountChangedEvent.postValue(
- Event(account.params.identityAddress?.asStringUriOnly() ?: "")
- )
}
@WorkerThread
- override fun onNewAccountAdded(core: Core, account: Account) {
+ override fun onAccountAdded(core: Core, account: Account) {
Log.i(
"$TAG Account [${account.params.identityAddress?.asStringUriOnly()}] has been added to the Core"
)
diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
index dda8f3548..85ec90ddb 100644
--- a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
@@ -172,23 +172,28 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
- override fun onDefaultAccountChanged(core: Core, account: Account) {
- Log.i(
- "$TAG Default account changed, now is [${account.params.identityAddress?.asStringUriOnly()}]"
- )
- removeAlert(NON_DEFAULT_ACCOUNT_NOTIFICATIONS)
-
- if (defaultAccountRegistrationFailed && account.state != RegistrationState.Failed) {
+ override fun onDefaultAccountChanged(core: Core, account: Account?) {
+ if (account == null) {
+ Log.w("$TAG Default account is now null!")
+ } else {
Log.i(
- "$TAG Newly set default account isn't in failed registration state, clearing alert"
+ "$TAG Default account changed, now is [${account.params.identityAddress?.asStringUriOnly()}]"
)
- defaultAccountRegistrationFailed = false
- defaultAccountRegistrationErrorEvent.postValue(Event(false))
- // Refresh REGISTER to re-compute alerts regarding accounts registration state
- core.refreshRegisters()
+ if (defaultAccountRegistrationFailed && account.state != RegistrationState.Failed) {
+ Log.i(
+ "$TAG Newly set default account isn't in failed registration state, clearing alert"
+ )
+ defaultAccountRegistrationFailed = false
+ defaultAccountRegistrationErrorEvent.postValue(Event(false))
+
+ // Refresh REGISTER to re-compute alerts regarding accounts registration state
+ core.refreshRegisters()
+ }
}
+ removeAlert(NON_DEFAULT_ACCOUNT_NOTIFICATIONS)
+
// TODO: compute other calls notifications count
}
}