I am creating a library that needs to check runtime permissions. I have got runtime permissions working fine and understand the use cases without issues.
However I would like to confirm that the developer using our library has added the permission to their manifest.
The library is a location based library and the developer can either enter ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION into the manifest and I need to be able to determine which they have used (or both) at runtime.
I though using the package manager to check permission would work however this always seems to fail:
PackageManager pm = getPackageManager();
int granted = pm.checkPermission(
Manifest.permission.ACCESS_COARSE_LOCATION,
getPackageName() );
if (granted == PackageManager.PERMISSION_GRANTED)
{
// Use coarse for runtime requests
}
// granted is always PackageManager.PERMISSION_DENIED
Is there some other way to do this in Android v23+?
Off the cuff, retrieve the PackageInfo via PackageManager and getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS). Then, look at the requestedPermissions array in the PackageInfo for all the <uses-permission>-requested permissions.
Thanks to answer of CommonsWare I'm created this method Kotlin to check if SMS permission is present on Manifest
fun hasSmsPermissionInManifest(context: Context): Boolean {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
val permissions = packageInfo.requestedPermissions
if (permissions.isNullOrEmpty())
return false
for (perm in permissions) {
if (perm == Manifest.permission.READ_SMS || perm == Manifest.permission.RECEIVE_SMS)
return true
}
return false
}
or
fun Context.hasSmsPermissionInManifest(): Boolean {
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
val permissions = packageInfo.requestedPermissions
return permissions?.any { perm -> perm == Manifest.permission.READ_SMS || perm == Manifest.permission.RECEIVE_SMS } ?: false
}
Related
In Android 13, I need a basic flow to get permission for push notifications:
class MainActivity : ComponentActivity(), LocationListener {
val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
}
private fun requestPushNotificationPermissions(){
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)) {
// granted
}else {
// not granted, ask for permission
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
This is what happened:
when user first installed the app, checkSelfPermission returns not granted, and we then lauch permission launcher
user sees the permission dialog in Android 13
user selects Allow
Expected: registerForActivityResult callback will be fired with isGranted true
Actual: registerForActivityResult callback is not fired.
Same if user selects Not Allow. callback is never fired. Why?
This is my dependencies:
implementation 'androidx.activity:activity-ktx:1.2.0-alpha07'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha07'
sadly can't help why it doesn't work. But I used EasyPermission for handling the permissons request and it works fine.
https://github.com/googlesamples/easypermissions
Turns out, the registerForActivityResult callback never fires, because somewhere in the Activity, there is this piece of old function "onRequestPermissionsResult" that is accidentally catching all permissions callback:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == locationPermissionCode) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Location permission granted
}
else {
//Location permission denied
}
}else{
//Notification permission callback accidentally landed here silently
}
}
Hope this helps someone.
Migrating from android 30 to the android 31 version, I am trying to connect to the paired Bluetooth device in my app.
But, I am facing an issue when I tried to get the paired device list.
val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
pairedDevices?.forEach { device ->
if(device.address.lowercase() == configHelper.bluetoothAddress.lowercase()){
bluetoothDevice = device
paymentTerminalName = device.name
paymentTerminalMAC = device.address // MAC address
UUIDFromPaymentTerminal = device.uuids[0].uuid // UUID specifies the service that the the server provides
}
}
if(bluetoothDevice == null){
//payment terminal is not paired yet
throw ex
}
socket = bluetoothDevice!!.createRfcommSocketToServiceRecord(UUIDFromPaymentTerminal)
//cancel discovery, otherwise it slows down the connection process
bluetoothAdapter!!.cancelDiscovery()
(socket!! as BluetoothSocket).connect()
I am getting the below error,
java.lang.SecurityException: Need android.permission.BLUETOOTH_CONNECT
permission for AttributionSource { uid = 10414, packageName =
com.test.app, attributionTag = null, token =
android.os.BinderProxy#8a4f87d, next = null }: AdapterService
getBondedDevices
I have added bluetooth permission to my manifest file,
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
Let me know where am I missing to get the paired device in kotlin 1.5.0
The BLUETOOTH_CONNECT & BLUETOOTH_SCAN permissions are runtime permissions, these permissions have to be asked during the runtime of your application.
The BLUETOOTH_SCAN permission should be asked before you scan for devices and the BLUETOOTH_CONNECT permission should be asked before you connect to a device.
An example of a permission requester for an single permission within the Fragments architecture:
val requestConnectPermission: ActivityResultLauncher<String> =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted ->
if (permissionGranted) {
// connect to device
}
}
An example of a requester for multiple permissions within the Fragments architecture:
val permissionRequester: ActivityResultLauncher<Array<String>> = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.values.all { it }) {
// connect to device
}
}
To handle a single permission request within an Composable is slightly different, you can do the following:
val permissionRequester =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
// connect to device
}
}
requestConnectPermission?.launch(Manifest.permission.BLUETOOTH_CONNECT)
And for multiple permissions at once:
val permissionRequester =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.values.all { it }) {
// connect to device
}
}
Before you should use such a permission launcher you should check if the user has already accepted the permissions. If not you can launch a request. An example to launch a single permission request:
if (ActivityCompat.checkSelfPermission(requireContext(), BLUETOOTH_CONNECT) == PERMISSION_GRANTED) {
// connect to device
} else {
permissionRequester.launch(BLUETOOTH_CONNECT)
}
And if you want to launch multiple permission requests at once you can do this:
if (ActivityCompat.checkSelfPermission(requireContext(), BLUETOOTH_CONNECT) == PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(requireContext(), BLUETOOTH_SCAN) == PERMISSION_GRANTED) {
// connect to device
} else {
permissionRequester.launch(arrayOf(BLUETOOTH_CONNECT, BLUETOOTH_SCAN))
}
More info about permissions can be found here.
In Android 11, when user select "deny" option for more than once for any permission request, then system will mark it as "Permanently denied".Once permanently denied, user has to enable in settings.From this time shouldShowRequestPermissionRationale() start's to return false
Three options are available for permission window , "Deny","Allow All time","Allow only this time". But in settings "Deny","Allow all the time","Ask every time" are present.
How to find when user selects "Ask me every time" from settings, because, checkSelfPermission() returns PackageManager.PERMISSION_DENIED,and shouldShowRequestPermissionRationale() return false. In this time I want to show permission window, instead of move to settings. Something similar to google map permission
Using the new ActivityResultsContract you can do this in the following manner
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result: MutableMap<String, Boolean> ->
val deniedList: List<String> = result.filter {
!it.value
}.map {
it.key
}
when {
deniedList.isNotEmpty() -> {
val map = deniedList.groupBy { permission ->
if (shouldShowRequestPermissionRationale(permission)) DENIED else EXPLAINED
}
map[DENIED]?.let {
// request denied , request again
}
map[EXPLAINED]?.let {
//request denied ,send to settings
}
}
else -> {
//All request are permitted
}
}
}
In OnCreate()[Make sure you ask permission in OnCreate , else application will crash] , ask the permission :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermissionLauncher.launch(REQUIRED_PERMISSIONS)
}
Ask requiredPermissions in the following manner :
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
)
Actually there is one more scenario:
if the user let you request the permission but don't chose any option and dismissed the dialog by tapping outside, the request finishes with denied and shouldShowRequestPermissionRationale() returns false.
That is the exact same behaviour as if the user selects don't ask again.
the permission where requested once, it results in denied and we should not show a explanation.
therefore we have to track if shouldShowRequestPermissionRationale() has returned true for once. if it switches back to false its denied permanent.
Use the following method in your activity (Camera permission is used in this example):
private fun requestPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
//Permission is denied
} else {
//ask permission
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA)
}
}
}
You only need to check the shouldShowRequestPermissionRationale() after user deny the permission
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
} else {
if(shouldShowRequestPermissionRationale(Manifest.permission.YOUR_RUNTIME_PERMISSION)){
//user hasn't denied permission permanently
}else{
//user has denied permanently,
//you should try to redirect user to settings
//to ask user grant it manually
}
}
}
requestPermissionLauncher.launch(Manifest.permission.YOUR_RUNTIME_PERMISSION)
I am using MediaStore to recieve the ids of all the images stored on the device using
private fun getImageUris(): List<Uri> {
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Images.Media._ID)
val mCursor = requireActivity().contentResolver.query(
uri,
projection,
null,
null,
null
)
val imageUris = mutableListOf<Uri>()
while (mCursor?.moveToNext() == true) {
val columnIndex = mCursor.getColumnIndex(MediaStore.Images.Media._ID)
val imageId = mCursor.getInt(columnIndex)
val currentImageUri = Uri.withAppendedPath(uri, imageId.toString())
imageUris.add(currentImageUri)
}
mCursor?.close()
return imageUris
}
and then i ask for the permission in onViewCreated() of my fragment like this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding = FragmentMainBinding.bind(view)
when {
isStoragePermissionGranted -> {
binding.recyclerView.adapter = RecyclerViewAdapter(getImageUris())
}
shouldShowRequestPermissionRationale -> {
Toast.makeText(mainActivity, "Please grant storage permission", Toast.LENGTH_SHORT).show()
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_REQUEST_CODE)
}
else -> {
Toast.makeText(mainActivity, "Permission not granted. Images cannot be shown.", Toast.LENGTH_SHORT).show()
}
}
}
I expect this to ask for the storage permission as soon as the app is opened (and my fragment is created) and then it should load all the images on the phone in my recycler view (I am using glide). But it doesn't work the way its expected to, instead the
"Permission not granted. Images cannot be shown."
toast message is shown
For a quick workaround, you can add this in the manifest file under <application>
android:requestLegacyExternalStorage="true"
Read about the storage update for Android 11 here
The problem was here as noted by #blackapps
shouldShowRequestPermissionRationale -> {
Toast.makeText(mainActivity, "Please grant storage permission", Toast.LENGTH_SHORT).show()
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_REQUEST_CODE)
because shouldShowRequestPermissionRationale is only true when the "deny and don't ask again" option is showing with the permission pop up and that option only appears after the first time the permission has been asked for (which never happens since in order for that to happen, shouldShowRequestPermissionRationale needs to be true).
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
use above permission and in application write below code:
<application
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"/
I am trying to remove a file from the local storage of my phone device.
I have given the path and filename and both exist.
I ran file.checkDir & file.checkFile to confirm whether its getting the path and it returned true.
I tried it on multiple android devices and observed that its only getting deleted for android versions below 8. I am not aware of any plugin update for the higher android version if there is any. I tried to google it but there is nowhere mentioned regarding plugin update.
Its throwing this error:
FileError {code: 6, message: “NO_MODIFICATION_ALLOWED_ERR”}
Although I have mentioned the permissions:
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_EXTERNAL_STORAGE
I am not sure about what I am doing wrong here.
Thanks for the help.
this.file.removeFile(path, fileName)
Expected - File should be removed from the given path
Actual - File not being removed from the given path
From sdk21, if Im not mistaken, not enough to specify permission in manifets. You have to request it in runtime and check that you have it any time, when you are going to use it. Smth like that
const val INTERET = Manifest.permission.INTERNET
const val READ = Manifest.permission.READ_EXTERNAL_STORAGE
const val WRITE = Manifest.permission.WRITE_EXTERNAL_STORAGE
const val LOCATION_COARSE = Manifest.permission.ACCESS_COARSE_LOCATION
const val LOCATION_FINE = Manifest.permission.ACCESS_FINE_LOCATION
const val PHONE = Manifest.permission.CALL_PHONE
fun granted(activity: Activity, vararg permission: String): Boolean {
val list = ArrayList<String>()
for (s in permission)
if (ActivityCompat.checkSelfPermission(activity, s) != PackageManager.PERMISSION_GRANTED)
list.add(s)
if (list.isEmpty())
return true
ActivityCompat.requestPermissions(activity, list.toArray(arrayOfNulls<String>(list.size)), 1)
return false
}
and in code check permission:
if(granted(this, READ, WHRITE)
this.file.removeFile(path, fileName)
else
//do smth if you have no permission
good for you to react if user denied permission. You can do it in Activity.onRequestPermissionsResult