Improved READ_PHONE_STATE / READ_PHONE_NUMBERS permissions usage

This commit is contained in:
Sylvain Berfini 2021-10-11 10:12:08 +02:00
parent c1dafcb9b9
commit 4337dd5da8
14 changed files with 125 additions and 27 deletions

View file

@ -20,13 +20,13 @@
package org.linphone.activities.assistant.fragments package org.linphone.activities.assistant.fragments
import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.linphone.R import org.linphone.R
import org.linphone.activities.GenericFragment import org.linphone.activities.GenericFragment
import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel
import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
@ -41,19 +41,19 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
) { ) {
if (requestCode == 0) { if (requestCode == 0) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 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() updateFromDeviceInfo()
} else { } else {
Log.w("[Assistant] READ_PHONE_STATE permission denied") Log.w("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission denied")
} }
} }
} }
protected fun checkPermission() { protected fun checkPermission() {
if (!resources.getBoolean(R.bool.isTablet)) { if (!resources.getBoolean(R.bool.isTablet)) {
if (!PermissionHelper.get().hasReadPhoneState()) { if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
Log.i("[Assistant] Asking for READ_PHONE_STATE permission") Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission")
requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0) Compatibility.requestReadPhoneStateOrNumbersPermission(requireActivity(), 0)
} else { } else {
updateFromDeviceInfo() updateFromDeviceInfo()
} }

View file

@ -111,7 +111,7 @@ class ControlsFragment : GenericFragment<CallControlsFragmentBinding>() {
viewLifecycleOwner, viewLifecycleOwner,
{ {
it.consume { it.consume {
if (!PermissionHelper.get().hasWriteExternalStorage()) { if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission") Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission")
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
} }

View file

@ -124,7 +124,7 @@ class CallsViewModel : ViewModel() {
} }
fun takeScreenshot() { fun takeScreenshot() {
if (!PermissionHelper.get().hasWriteExternalStorage()) { if (!PermissionHelper.get().hasWriteExternalStoragePermission()) {
askWriteExternalStoragePermissionEvent.value = Event(true) askWriteExternalStoragePermissionEvent.value = Event(true)
} else { } else {
currentCallViewModel.value?.takeScreenshot() currentCallViewModel.value?.takeScreenshot()

View file

@ -402,7 +402,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
binding.setAttachFileClickListener { binding.setAttachFileClickListener {
if (PermissionHelper.get().hasReadExternalStorage() && PermissionHelper.get().hasCameraPermission()) { if (PermissionHelper.get().hasReadExternalStoragePermission() && PermissionHelper.get().hasCameraPermission()) {
pickFile() pickFile()
} else { } else {
Log.i("[Chat Room] Asking for READ_EXTERNAL_STORAGE and CAMERA permissions") Log.i("[Chat Room] Asking for READ_EXTERNAL_STORAGE and CAMERA permissions")

View file

@ -70,7 +70,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
return return
} }
if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10) && !PermissionHelper.get().hasWriteExternalStorage()) { if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10) && !PermissionHelper.get().hasWriteExternalStoragePermission()) {
for (content in chatMessage.contents) { for (content in chatMessage.contents) {
if (content.isFileTransfer) { if (content.isFileTransfer) {
Log.i("[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet") Log.i("[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet")

View file

@ -19,11 +19,14 @@
*/ */
package org.linphone.activities.main.dialer.fragments package org.linphone.activities.main.dialer.fragments
import android.Manifest
import android.annotation.TargetApi
import android.app.Dialog import android.app.Dialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -44,9 +47,11 @@ import org.linphone.activities.navigateToConfigFileViewer
import org.linphone.activities.navigateToContacts import org.linphone.activities.navigateToContacts
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.DialerFragmentBinding import org.linphone.databinding.DialerFragmentBinding
import org.linphone.mediastream.Version
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.DialogUtils import org.linphone.utils.DialogUtils
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.PermissionHelper
class DialerFragment : SecureFragment<DialerFragmentBinding>() { class DialerFragment : SecureFragment<DialerFragmentBinding>() {
private lateinit var viewModel: DialerViewModel private lateinit var viewModel: DialerViewModel
@ -164,6 +169,10 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer
checkForUpdate() checkForUpdate()
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
checkPermissions()
}
} }
override fun onPause() { override fun onPause() {
@ -185,6 +194,28 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
viewModel.enteredUri.value = sharedViewModel.dialerUri viewModel.enteredUri.value = sharedViewModel.dialerUri
} }
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
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() { private fun displayDebugPopup() {
val alertDialog = MaterialAlertDialogBuilder(requireContext()) val alertDialog = MaterialAlertDialogBuilder(requireContext())
alertDialog.setTitle(getString(R.string.debug_popup_title)) alertDialog.setTitle(getString(R.string.debug_popup_title))

View file

@ -69,7 +69,7 @@ class Api21Compatibility {
} }
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { 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") Log.e("[Media Store] Write external storage permission denied")
return false return false
} }
@ -100,7 +100,7 @@ class Api21Compatibility {
} }
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { 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") Log.e("[Media Store] Write external storage permission denied")
return false return false
} }
@ -132,7 +132,7 @@ class Api21Compatibility {
} }
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { 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") Log.e("[Media Store] Write external storage permission denied")
return false return false
} }

View file

@ -19,7 +19,9 @@
*/ */
package org.linphone.compatibility package org.linphone.compatibility
import android.Manifest
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.Activity
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.ContentValues import android.content.ContentValues
@ -45,6 +47,20 @@ import org.linphone.utils.LinphoneUtils
@TargetApi(29) @TargetApi(29)
class Api29Compatibility { class Api29Compatibility {
companion object { 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( fun createMessageChannel(
context: Context, context: Context,
notificationManager: NotificationManagerCompat notificationManager: NotificationManagerCompat

View file

@ -19,15 +19,32 @@
*/ */
package org.linphone.compatibility package org.linphone.compatibility
import android.Manifest
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
@TargetApi(30) @TargetApi(30)
class Api30Compatibility { class Api30Compatibility {
companion object { 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) { fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) {
val peerAddress = chatRoom.peerAddress.asStringUriOnly() val peerAddress = chatRoom.peerAddress.asStringUriOnly()
val localAddress = chatRoom.localAddress.asStringUriOnly() val localAddress = chatRoom.localAddress.asStringUriOnly()

View file

@ -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 { fun getDeviceName(context: Context): String {
return when (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) { return when (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) {
true -> Api25Compatibility.getDeviceName(context) true -> Api25Compatibility.getDeviceName(context)

View file

@ -313,13 +313,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
configureCore() configureCore()
try { initPhoneStateListener()
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")
}
EmojiCompat.init(BundledEmojiCompatConfig(context)) EmojiCompat.init(BundledEmojiCompatConfig(context))
collator.strength = Collator.NO_DECOMPOSITION collator.strength = Collator.NO_DECOMPOSITION
@ -410,6 +404,21 @@ class CoreContext(val context: Context, coreConfig: Config) {
/* Call related functions */ /* 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) { fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) {
val params = core.createCallParams(call) val params = core.createCallParams(call)
@ -655,7 +664,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
return 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) { for (content in message.contents) {
if (content.isFile && content.filePath != null && content.userData == null) { if (content.isFile && content.filePath != null && content.userData == null) {
addContentToMediaStore(content) addContentToMediaStore(content)
@ -676,7 +685,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
return return
} }
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStorage()) { if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) {
coroutineScope.launch { coroutineScope.launch {
when (content.type) { when (content.type) {
"image" -> { "image" -> {

View file

@ -81,7 +81,7 @@ class TelecomHelper private constructor(context: Context) {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
fun findExistingAccount(context: Context): PhoneAccount? { fun findExistingAccount(context: Context): PhoneAccount? {
if (!PermissionHelper.exists()) PermissionHelper.create(context) if (!PermissionHelper.exists()) PermissionHelper.create(context)
if (PermissionHelper.get().hasReadPhoneState()) { if (PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
var account: PhoneAccount? = null var account: PhoneAccount? = null
val phoneAccountHandleList: List<PhoneAccountHandle> = val phoneAccountHandleList: List<PhoneAccountHandle> =
telecomManager.selfManagedPhoneAccounts telecomManager.selfManagedPhoneAccounts

View file

@ -21,6 +21,8 @@ package org.linphone.utils
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import org.linphone.compatibility.Compatibility import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
@ -50,15 +52,19 @@ class PermissionHelper private constructor(private val context: Context) {
return hasPermission(Manifest.permission.WRITE_CONTACTS) return hasPermission(Manifest.permission.WRITE_CONTACTS)
} }
fun hasReadPhoneState(): Boolean { fun hasReadPhoneStatePermission(): Boolean {
return hasPermission(Manifest.permission.READ_PHONE_STATE) 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) return hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
} }
fun hasWriteExternalStorage(): Boolean { fun hasWriteExternalStoragePermission(): Boolean {
return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) 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) return hasPermission(Manifest.permission.RECORD_AUDIO)
} }
@RequiresApi(Build.VERSION_CODES.O)
fun hasTelecomManagerPermissions(): Boolean { fun hasTelecomManagerPermissions(): Boolean {
return hasPermission(Manifest.permission.READ_PHONE_NUMBERS) && return hasPermission(Manifest.permission.READ_PHONE_NUMBERS) &&
hasPermission(Manifest.permission.MANAGE_OWN_CALLS) hasPermission(Manifest.permission.MANAGE_OWN_CALLS)

View file

@ -41,7 +41,7 @@ class PhoneNumberUtils {
@SuppressLint("MissingPermission", "HardwareIds") @SuppressLint("MissingPermission", "HardwareIds")
fun getDevicePhoneNumber(context: Context): String? { fun getDevicePhoneNumber(context: Context): String? {
if (PermissionHelper.get().hasReadPhoneState()) { if (PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
try { try {
val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return tm.line1Number return tm.line1Number