From 4337dd5da8d06c0773254d73d3829ea90d9029e6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 11 Oct 2021 10:12:08 +0200 Subject: [PATCH] Improved READ_PHONE_STATE / READ_PHONE_NUMBERS permissions usage --- .../fragments/AbstractPhoneFragment.kt | 12 +++---- .../call/fragments/ControlsFragment.kt | 2 +- .../call/viewmodels/CallsViewModel.kt | 2 +- .../chat/fragments/DetailChatRoomFragment.kt | 2 +- .../viewmodels/ChatMessagesListViewModel.kt | 2 +- .../main/dialer/fragments/DialerFragment.kt | 31 +++++++++++++++++++ .../compatibility/Api21Compatibility.kt | 6 ++-- .../compatibility/Api29Compatibility.kt | 16 ++++++++++ .../compatibility/Api30Compatibility.kt | 17 ++++++++++ .../linphone/compatibility/Compatibility.kt | 18 +++++++++++ .../java/org/linphone/core/CoreContext.kt | 27 ++++++++++------ .../org/linphone/telecom/TelecomHelper.kt | 2 +- .../org/linphone/utils/PermissionHelper.kt | 13 ++++++-- .../org/linphone/utils/PhoneNumberUtils.kt | 2 +- 14 files changed, 125 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt index 4941ac2c9..7b92f5652 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt @@ -20,13 +20,13 @@ package org.linphone.activities.assistant.fragments -import android.Manifest import android.content.pm.PackageManager import androidx.databinding.ViewDataBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.linphone.R import org.linphone.activities.GenericFragment import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel +import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log import org.linphone.utils.PermissionHelper import org.linphone.utils.PhoneNumberUtils @@ -41,19 +41,19 @@ abstract class AbstractPhoneFragment : GenericFragment() ) { if (requestCode == 0) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Assistant] READ_PHONE_STATE permission granted") + Log.i("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission granted") updateFromDeviceInfo() } else { - Log.w("[Assistant] READ_PHONE_STATE permission denied") + Log.w("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission denied") } } } protected fun checkPermission() { if (!resources.getBoolean(R.bool.isTablet)) { - if (!PermissionHelper.get().hasReadPhoneState()) { - Log.i("[Assistant] Asking for READ_PHONE_STATE permission") - requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0) + if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { + Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission") + Compatibility.requestReadPhoneStateOrNumbersPermission(requireActivity(), 0) } else { updateFromDeviceInfo() } diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt index fee9e5f2a..71f7d03dc 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt @@ -111,7 +111,7 @@ class ControlsFragment : GenericFragment() { viewLifecycleOwner, { it.consume { - if (!PermissionHelper.get().hasWriteExternalStorage()) { + if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission") requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) } diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt index e8a6a61a1..2372b756d 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt @@ -124,7 +124,7 @@ class CallsViewModel : ViewModel() { } fun takeScreenshot() { - if (!PermissionHelper.get().hasWriteExternalStorage()) { + if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { askWriteExternalStoragePermissionEvent.value = Event(true) } else { currentCallViewModel.value?.takeScreenshot() diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index e5cebb6c7..10882e4ae 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -402,7 +402,7 @@ class DetailChatRoomFragment : MasterFragment() { private lateinit var viewModel: DialerViewModel @@ -164,6 +169,10 @@ class DialerFragment : SecureFragment() { viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer checkForUpdate() + + if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { + checkPermissions() + } } override fun onPause() { @@ -185,6 +194,28 @@ class DialerFragment : SecureFragment() { viewModel.enteredUri.value = sharedViewModel.dialerUri } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode == 0) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.i("[Dialer] READ_PHONE_STATE permission has been granted") + coreContext.initPhoneStateListener() + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + @TargetApi(Version.API23_MARSHMALLOW_60) + private fun checkPermissions() { + if (!PermissionHelper.get().hasReadPhoneStatePermission()) { + Log.i("[Dialer] Asking for READ_PHONE_STATE permission") + requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0) + } + } + private fun displayDebugPopup() { val alertDialog = MaterialAlertDialogBuilder(requireContext()) alertDialog.setTitle(getString(R.string.debug_popup_title)) diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index 76cc93000..cd59c2076 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -69,7 +69,7 @@ class Api21Compatibility { } suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { - if (!PermissionHelper.get().hasWriteExternalStorage()) { + if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { Log.e("[Media Store] Write external storage permission denied") return false } @@ -100,7 +100,7 @@ class Api21Compatibility { } suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { - if (!PermissionHelper.get().hasWriteExternalStorage()) { + if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { Log.e("[Media Store] Write external storage permission denied") return false } @@ -132,7 +132,7 @@ class Api21Compatibility { } suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { - if (!PermissionHelper.get().hasWriteExternalStorage()) { + if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { Log.e("[Media Store] Write external storage permission denied") return false } diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index c7608e047..fbdad96b2 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -19,7 +19,9 @@ */ package org.linphone.compatibility +import android.Manifest import android.annotation.TargetApi +import android.app.Activity import android.app.NotificationChannel import android.app.NotificationManager import android.content.ContentValues @@ -45,6 +47,20 @@ import org.linphone.utils.LinphoneUtils @TargetApi(29) class Api29Compatibility { companion object { + fun hasReadPhoneStatePermission(context: Context): Boolean { + val granted = Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) + if (granted) { + Log.d("[Permission Helper] Permission READ_PHONE_STATE is granted") + } else { + Log.w("[Permission Helper] Permission READ_PHONE_STATE is denied") + } + return granted + } + + fun requestReadPhoneStatePermission(activity: Activity, code: Int) { + activity.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), code) + } + fun createMessageChannel( context: Context, notificationManager: NotificationManagerCompat diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt index 75cfe234a..d785cbea8 100644 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt @@ -19,15 +19,32 @@ */ package org.linphone.compatibility +import android.Manifest import android.annotation.TargetApi +import android.app.Activity import android.content.Context import android.content.pm.ShortcutManager import org.linphone.core.ChatRoom +import org.linphone.core.tools.Log import org.linphone.utils.LinphoneUtils @TargetApi(30) class Api30Compatibility { companion object { + fun hasReadPhoneNumbersPermission(context: Context): Boolean { + val granted = Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_NUMBERS) + if (granted) { + Log.d("[Permission Helper] Permission READ_PHONE_NUMBERS is granted") + } else { + Log.w("[Permission Helper] Permission READ_PHONE_NUMBERS is denied") + } + return granted + } + + fun requestReadPhoneNumbersPermission(activity: Activity, code: Int) { + activity.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_NUMBERS), code) + } + fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) { val peerAddress = chatRoom.peerAddress.asStringUriOnly() val localAddress = chatRoom.localAddress.asStringUriOnly() diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index 26bbcb44a..8079c1c86 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -44,6 +44,24 @@ class Compatibility { } } + // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers + fun hasReadPhoneStateOrNumbersPermission(context: Context): Boolean { + return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { + Api30Compatibility.hasReadPhoneNumbersPermission(context) + } else { + Api29Compatibility.hasReadPhoneStatePermission(context) + } + } + + // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers + fun requestReadPhoneStateOrNumbersPermission(activity: Activity, code: Int) { + if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { + Api30Compatibility.requestReadPhoneNumbersPermission(activity, code) + } else { + Api29Compatibility.requestReadPhoneStatePermission(activity, code) + } + } + fun getDeviceName(context: Context): String { return when (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) { true -> Api25Compatibility.getDeviceName(context) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 81426b886..db8696476 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -313,13 +313,7 @@ class CoreContext(val context: Context, coreConfig: Config) { configureCore() - try { - phoneStateListener = - Compatibility.createPhoneListener(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) - } catch (exception: SecurityException) { - val hasReadPhoneStatePermission = PermissionHelper.get().hasReadPhoneState() - Log.e("[Context] Failed to create phone state listener: $exception, READ_PHONE_STATE permission status is $hasReadPhoneStatePermission") - } + initPhoneStateListener() EmojiCompat.init(BundledEmojiCompatConfig(context)) collator.strength = Collator.NO_DECOMPOSITION @@ -410,6 +404,21 @@ class CoreContext(val context: Context, coreConfig: Config) { /* Call related functions */ + fun initPhoneStateListener() { + if (PermissionHelper.get().hasReadPhoneStatePermission()) { + try { + phoneStateListener = + Compatibility.createPhoneListener(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) + } catch (exception: SecurityException) { + val hasReadPhoneStatePermission = + PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission() + Log.e("[Context] Failed to create phone state listener: $exception, READ_PHONE_STATE permission status is $hasReadPhoneStatePermission") + } + } else { + Log.w("[Context] Can't create phone state listener, READ_PHONE_STATE permission isn't granted") + } + } + fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) { val params = core.createCallParams(call) @@ -655,7 +664,7 @@ class CoreContext(val context: Context, coreConfig: Config) { return } - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStorage()) { + if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) { for (content in message.contents) { if (content.isFile && content.filePath != null && content.userData == null) { addContentToMediaStore(content) @@ -676,7 +685,7 @@ class CoreContext(val context: Context, coreConfig: Config) { return } - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStorage()) { + if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) { coroutineScope.launch { when (content.type) { "image" -> { diff --git a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt index 481d8d220..f9e601b34 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt @@ -81,7 +81,7 @@ class TelecomHelper private constructor(context: Context) { @SuppressLint("MissingPermission") fun findExistingAccount(context: Context): PhoneAccount? { if (!PermissionHelper.exists()) PermissionHelper.create(context) - if (PermissionHelper.get().hasReadPhoneState()) { + if (PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { var account: PhoneAccount? = null val phoneAccountHandleList: List = telecomManager.selfManagedPhoneAccounts diff --git a/app/src/main/java/org/linphone/utils/PermissionHelper.kt b/app/src/main/java/org/linphone/utils/PermissionHelper.kt index 94317f4dd..40fad7073 100644 --- a/app/src/main/java/org/linphone/utils/PermissionHelper.kt +++ b/app/src/main/java/org/linphone/utils/PermissionHelper.kt @@ -21,6 +21,8 @@ package org.linphone.utils import android.Manifest import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log @@ -50,15 +52,19 @@ class PermissionHelper private constructor(private val context: Context) { return hasPermission(Manifest.permission.WRITE_CONTACTS) } - fun hasReadPhoneState(): Boolean { + fun hasReadPhoneStatePermission(): Boolean { return hasPermission(Manifest.permission.READ_PHONE_STATE) } - fun hasReadExternalStorage(): Boolean { + fun hasReadPhoneStateOrPhoneNumbersPermission(): Boolean { + return Compatibility.hasReadPhoneStateOrNumbersPermission(context) + } + + fun hasReadExternalStoragePermission(): Boolean { return hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) } - fun hasWriteExternalStorage(): Boolean { + fun hasWriteExternalStoragePermission(): Boolean { return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) } @@ -70,6 +76,7 @@ class PermissionHelper private constructor(private val context: Context) { return hasPermission(Manifest.permission.RECORD_AUDIO) } + @RequiresApi(Build.VERSION_CODES.O) fun hasTelecomManagerPermissions(): Boolean { return hasPermission(Manifest.permission.READ_PHONE_NUMBERS) && hasPermission(Manifest.permission.MANAGE_OWN_CALLS) diff --git a/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt b/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt index a9b0724b0..4cf8f5866 100644 --- a/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt +++ b/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt @@ -41,7 +41,7 @@ class PhoneNumberUtils { @SuppressLint("MissingPermission", "HardwareIds") fun getDevicePhoneNumber(context: Context): String? { - if (PermissionHelper.get().hasReadPhoneState()) { + if (PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { try { val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager return tm.line1Number