I am very new to Android programming and I am having trouble requesting storage permission. The idea is to have the phone generate a small label that will print from a mobile printer via Bluetooth, but as proof of concept I was going to have the phone just save a PDF or something for now.
I added this line to the AndroidManifest.xml:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:remove="android:maxSDKVersion" />
and from the MainActivity.kt:
class MainActivity : AppCompatActivity() {
private val REQUEST_STORAGE = 101
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)
{
println("Permissions Denied")
requestStoragePermission()
println("Passed Command")
} else {
println("PERMISSIONS GRANTED")
}
private fun requestStoragePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
permission.WRITE_EXTERNAL_STORAGE
)
) {
requestPermissions(this, arrayOf(permission.WRITE_EXTERNAL_STORAGE), REQUEST_STORAGE)
} else {
// Eh, prompt anyway
requestPermissions(this, arrayOf(permission.WRITE_EXTERNAL_STORAGE), REQUEST_STORAGE)
}
}
}
No matter what iteration of the requestPermissions command I try, the dialog box never shows and attempting to save any file results in a failure. I know this question has been asked a lot, but none of the solutions that work for other people are working here and I'm not sure why.
I have tried:
\\this is latest iteration
requestPermissions(this, arrayOf(permission.WRITE_EXTERNAL_STORAGE), REQUEST_STORAGE)
\\this was first iteration
ActivityCompact.requestPermissions(
this#MainActivity,
arrayOf(permission.WRITE_EXTERNAL_SOTRAGE),
REQUEST_STORAGE
)
\\this one also caused an error so I abandoned the idea of moving this out of the main class
ActivityCompact.requestPermissions(
MainActivity(),
arrayOf(permission.WRITE_EXTERNAL_SOTRAGE),
REQUEST_STORAGE
)
I need the user to be able to give storage access, at least while it's still in development to convince my boss to buy a mobile printer I can use to print the actual label.
I have started default google sample project related to request runtime permissions topic. By default it uses camera permission. When I had changed Camera permission to WRITE_EXTERNAL_STORAGE permission it stops showing permission dialog too. It means nothing is wrong with your code, the issue has some different cause.
Likely it is related to changes in behaviour related to new Android API.
I haven't found root cause yet, but this links shows changes in behaviour on Android 9, 10, 11:
Different use cases
No need in permission since API 19
Variety of changes among different APIs
UPDATE
Code above doesn't show dialog for MANAGE_EXTERNAL_STORAGE permission, but you have asked for WRITE_EXTERNAL_STORAGE permission. Default google sample is able to show this permission dialog on Android API 30. Here is relevant code:
if (shouldShowRequestPermissionRationaleCompat(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// Display a SnackBar with a button to request the missing permission.
layout.showSnackbar(R.string.camera_access_required,
Snackbar.LENGTH_INDEFINITE, R.string.ok) {
requestPermissionsCompat(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CAMERA)
}
} else {
layout.showSnackbar(R.string.camera_permission_not_available, Snackbar.LENGTH_SHORT)
// Request the permission. The result will be received in onRequestPermissionResult().
requestPermissionsCompat(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CAMERA)
}
fun AppCompatActivity.requestPermissionsCompat(permissionsArray: Array<String>,
requestCode: Int) {
ActivityCompat.requestPermissions(this, permissionsArray, requestCode)
}
Related
My app requires precise user location because it requires the user to be at specific locations to perform tasks. I have followed the documentation for the proper way to request both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION together.
class LocationChooserFragment : BottomSheetDialogFragment() {
// only relevant parts shown
/**
* A utility class that holds all the code for requesting location updates,
* so that we can re-use it in multiple fragments.
*/
private var locationTracker: LocationTracker? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// all the other initialization for viewbinding
tryToGetLocationUpdates()
}
private fun tryToGetLocationUpdates() {
val request = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true && permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true) {
requestLocationUpdates()
}
}
when {
ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED -> {
Timber.i("Already have required permissions")
requestLocationUpdates()
}
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) ||
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION) -> {
Timber.i("Showing permission rationale dialog")
// A wrapper to show a dialog that explains what we need permissions for
alertDialog(
title = getString(R.string.permissions),
msg = getString(R.string.why_we_need_location),
onClick = { yes ->
if (yes) {
request.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
}
}
)
}
else -> {
Timber.i("Prompting for location permissions")
request.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
}
}
}
private fun requestLocationUpdates() {
if (locationProvider == null) {
locationProvider = LocationProvider(requireContext())
}
locationProvider?.requestLocationUpdates()
}
}
The problem I have is that my users are not getting prompted to allow precise location.
I expect to see the prompt in Figure 3 from the documentation:
Figure 3. System permissions dialog that appears when your app requests both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION in a single runtime request.
Instead, I see the prompt in Figure 2 from the documentation:
Figure 2. System permissions dialog that appears when your app requests ACCESS_COARSE_LOCATION only.
Additionally, I expect that when I already have COARSE permission and I request FINE and COARSE permission again, I should see the "Upgrade to Precise permission" dialog, but I don't see any prompt at all.
I have tested this on Pixel 3a (physical device and emulator) as well as received reports from other users on various Pixel and Samsung Galaxy devices that are running Android 12.
The problem was not present when we used compileSdk 30 and targetSdkVersion 30 in build.gradle, but we need to be able to support new features and new versions of Android (and to be able to upgrade to dependencies that only support building with newer SDK versions.
Why is the permission dialog not showing properly?
Comparing my AndroidManifest.xml to the generated file in the built app (using the "Analyze APK" feature in Android Studio), I discovered that the generated APK file had the following entry:
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
Clearly, the maxSdkVersion is preventing the OS from giving me the permission I need.
Using grep on the app\build directory, I found app\build\intermediates\manifest_merge_blame_file\appnameDebug\manifest-merger-blame-appname-debug-report.txt, which included these lines:
17 <uses-permission
17-->C:\path\to\my\appname\app\src\main\AndroidManifest.xml:12:2-76
18 android:name="android.permission.ACCESS_FINE_LOCATION"
18-->C:\path\to\my\appname\app\src\main\AndroidManifest.xml:12:19-73
19 android:maxSdkVersion="30" />
19-->[com.github.50ButtonsEach:flic2lib-android:1.3.1] C:\Users\moshe\.gradle\caches\transforms-3\dced3886509296f1029e8245af379a07\transformed\jetified-flic2lib-android-1.3.1\AndroidManifest.xml:17:9-35
It turns out the developers of this library originally used ACCESS_FINE_LOCATION for connecting to nearby Bluetooth devices, and since Android 12 has separate permissions for that, they don't need it anymore so they added a max SDK version.
I added the tools:remove="android:maxSdkVersion" property to the ACCESS_FINE_LOCATION <uses-permission /> element, and now my permissions work properly.
Using expo version 37..0.12
While trying to load multi-images there is a permission error reading the images
Following is the error
Permissions.askAsync(Permissions.CAMERA,Permissions.CAMERA_ROLL, Permissions.READ_EXTERNAL_STORAGE);
Error: [Unhandled promise rejection: Error: Failed to get permissions]
This happens in Android while using multi-image picker library: "expo-image-picker-multiple": "1.4.0"
I assume you already used this permission in your manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />.
If yes its probably because you don't have runtime permissions in your project like this
val requestPermissionLauncher =
registerForActivityResult(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
// features 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.
}
}
Ref: Android Docs for Runtime Permission
I have issues with my app recently, when it is out of nowhere rejected by Google Play because they found that I'm using background location. But in fact I'm not using this feature. I have only ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions and I'm using FusedLocationProviderClient to get location in my app. This location is requested only by user action inside app, so if its in background, this is never called. I checked merged manifest feature and I tried to find if some of my imported libs are using background location permission, but I didn't find anything. Also I preventively added <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove"/> to my manifest to block any background location permission requests. I dont have any background services which are working with location at all. The only background service is FirebaseMessagingService for push notifications.
Anyone have this problem recently?
UPDATE:
I checked merged manifest in my app and I couldn't find ACCESS_BACKGROUND_LOCATION permission there. But I found some services which could trigger background location but I'm not sure. They are part of Firebase Crashlytics and they are probably used to send data to Firebase and they could work in a background. But I don't think they are sending any location. Also they are part of firebase plugin which is from Google.
<service
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<receiver
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerSchedulerBroadcastReceiver"
android:exported="false" />
UPDATE #2:
This is code I'm using to get location.
MainActivity:
/**
* Updating location every second/1 meter
*/
var currLocation: GpsLocation? = null
private var locationManager : LocationManager? = null
private fun initLocationManager() {
if (app.hasLocationPermission){
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
changeLocationUpdaters(true)
}
private fun changeLocationUpdaters(isEnabled: Boolean){
if (ActivityCompat.checkSelfPermission(
this#MainActivity,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this#MainActivity,
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
locationManager?.apply{
if (isEnabled && app.hasLocationPermission){
requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_UPDATE_TIME_INTERVAL, LOCATION_UPDATE_DIST_INTERVAL, this#MainActivity)
requestLocationUpdates(LocationManager.NETWORK_PROVIDER, LOCATION_UPDATE_TIME_INTERVAL, LOCATION_UPDATE_DIST_INTERVAL, this#MainActivity)
} else {
removeUpdates(this#MainActivity)
}
}
} else {
return
}
}
Then removing location updaters when app is in background:
override fun onPause() {
super.onPause()
changeLocationUpdaters(false)
}
override fun onResume() {
super.onResume()
changeLocationUpdaters(true)
}
Then I use FusedLocationProvider inside Fragment to get more accurate location. Its used only by calling function so its not automated like previous one. Its used in GoogleMap classes and also in some onClick events inside app to return current location. There is no service or updater calling it.
private inner class LocationCb(val lp: FusedLocationProviderClient,
val onFailure: (()->Unit)? = null,
val onSuccess: (GpsLocation)->Unit)
: LocationCallback() {
init {
val lr = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 200
}
val lsr = LocationSettingsRequest.Builder().run {
addLocationRequest(lr)
build()
}
val check = LocationServices.getSettingsClient(activity!!).checkLocationSettings(lsr)
check.addOnCompleteListener {
try {
check.getResult(ApiException::class.java)
val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
task.addOnFailureListener {
onFailure?.invoke()
}
} catch (e: ApiException) {
when (e.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED-> if(!locationResolutionAsked){
// Location settings are not satisfied. But could be fixed by showing the user a dialog.
try {
// Cast to a resolvable exception.
val re = e as ResolvableApiException
// Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult().
re.startResolutionForResult(mainActivity, MainActivity.REQUEST_LOCATION_SETTINGS)
locationResolutionAsked = true
} catch (e: Exception) {
e.printStackTrace()
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE->{
App.warn("Location is not available")
onFailure?.invoke()
}
}
}
}
}
fun cancel(){
lp.removeLocationUpdates(this)
currLocCb = null
}
override fun onLocationResult(lr: LocationResult) {
cancel()
val ll = lr.lastLocation
onSuccess(GpsLocation(ll.longitude, ll.latitude))
}
}
This location provider is cancelled after result is returned so its one-time use only. But Ive added similar cancellation method inside onPause and onStop for Fragment than it is in MainActivity to make sure that its inactive when app is in background.
override fun onStop() {
super.onStop()
currLocCb?.cancel()
}
override fun onPause() {
super.onPause()
currLocCb?.cancel()
}
Merged manifest may not contain all permissions
Unfortunately, not all libraries publish a manifest that contains all necessary <uses-permission> elements. That means, that simply checking your merged AndroidManifest.xml won't help much - you will have to check documentation for each library to find out which permissions it really needs, or just add necessary permissions to your own AndroidManifest.xml preemptively.
Background permission limitation for API 29
You also mentioned that your target SDK is 29. So, according to the official documentation here, you have to set the permission in your AndroidManifest.xml explicitly, if it's needed. Previously, it was granted automatically, if the app had foreground location access (basically, ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION).
On Android 10 (API level 29) and higher, you must declare the
ACCESS_BACKGROUND_LOCATION permission in your app's manifest in order
to request background location access at runtime. On earlier versions
of Android, when your app receives foreground location access, it
automatically receives background location access as well.
So, for older versions, your app was granted ACCESS_BACKGROUND_LOCATION automatically, because it was granted ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION beforehand.
Requesting location in background requires ACCESS_BACKGROUND_LOCATION
Additionally, even if you or any of your libraries do not set ACCESS_BACKGROUND_LOCATION anywhere, the system will still consider that your app is using background location for any situation except:
An activity that belongs to your app is visible.
Your app is running a
foreground service. When a foreground service is running, the system
raises user awareness by showing a persistent notification. Your app
retains access when it's placed in the background, such as when the
user presses the Home button on their device or turns their device's
display off.
Conclusion
What the latter means is that may have a library or libraries that need ACCESS_BACKGROUND_LOCATION, but it's not present in their AndroidManifest.xml for whatever reason. It used to work for API < 29 because your app was granted the permission automatically (due to foreground location permission).
Also, now, the system considers any usage of current location a background location if it's done outside of your visible Activity or not in a Foreground Service. So, make sure that you're not doing so in any part of your app.
Update
Based on your updated question, you are requesting a current location within OnCompleteListener by calling lp.requestLocationUpdates:
...
check.addOnCompleteListener {
try {
check.getResult(ApiException::class.java)
val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
task.addOnFailureListener {
onFailure?.invoke()
}
...
This can be a problem (I cannot be sure because you don't show how the class is used within your app) because the app may go to the background before OnCompleteListener completes, and so the location will be requested in the background.
As stated in the previous section, by doing so the system considers that you need a background location permissions to do so. So, you must unsubscribe your callback OnCompleteListener if your app goes to background.
You could use another version of addOnCompleteListener that also accept your Activity instance as shown here
public Task addOnCompleteListener (Activity activity, OnCompleteListener listener)
In this case, the listener will be automatically removed during Activity.onStop().
First of all, remove completely words ACCESS_BACKGROUND_LOCATION from your manifest. Even with tools:node="remove".
The second: if you haven't added ACCESS_BACKGROUND_LOCATION manually it doesn't mean it is not there - some libraries may have added it for you. Instead of checking your project manifest file - check merged manifest - the usual path to it is: (it may be different in your case if you have flavors)
/project/module/build/intermediates/manifests/full/debug/AndroidManifest.xml.
Check if there is ACCESS_BACKGROUND_LOCATION permission there - if there is - this means that some library added it there. Manually check all the manifests of all the libraries to find out which one has added it there. When you find it - delete it.
If your project heavily depends on the target library - you have another solution - write a disclosure in the app and play store console about why do you need to use background location and show it before the location permission dialog with message that looks like:
We need access to your location in the background to ensure our app can function correctly.
Keep in mind that this message may be not enough descriptive - but testers from google will notify you if it is.
Either way, disclosure is the last chance solution...
If you have no ACCESS_BACKGROUND_LOCATION and you do not use location in foreground service but only inside the app while it is running - write a letter to google support with all your arguments and ask them what exactly causes the rejection issue. Be polite and well-tempered - and it will be resolved. I have had similar issues in the past and contact with their release support always helped.
I want to avoid ask others permission if the user not grant one.
For example: I ask for read/write external storage and camera, if the user deny the first, the library ask for camera permission, camera permission is redundant in this case.
RxPermissions.getInstance(getActivity()).request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
.subscribe(granted -> {
if (granted) {
dispatchCamera();
} else {
showError();
}
});
I think I could use requestEach, but I dont know how stop to emit items if one permission is not granted and also I cant obtain the final result, if the user grant all permissions.
I suppose your problem was solved, but I gonna place answer, for somebody else, who get into same trouble.
All you need is takeUntil or takeWhile operator with requestEach(depends what stream behaviour you want). TakeUntil unsubscribe you from stream, and bring you only events with granted permissions and one of permission denied inclusive.If you want get only permission granted events without any of them == denied, pls use takeWhile opertator.
Just maybe you would have to experement with this code a while, cause I didnt use this library yet, and maybe something would behave not exactly as you want.
So code might be like this:
RxPermissions
.getInstance(getActivity())
.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA)
.takeWhile(permission -> permission.granted)
//or use take untill if you want to get permission.granted == false event
//.takeUntil(permission -> !permission.granted)
.subscribe(granted -> {
if (granted) {
dispatchCamera();
} else {
showError();
}
});
In the main activity of an Android application I check for permissions (Manifest.permission.MANAGE_DOCUMENTS), detect I do not have them, and call requestPermisions.
Then in onRequestPermissionResult I almost immediately get denied permission, without a dialog ever showing.
I already confirmed the same permission in another activity of the same app (through requestPermissions again, which works), so I expected this decision to be stored (for the session, or whatever), and I have never selected to deny the permission. Either way, the permission dialog is not displayed and the permission is denied automatically.
So far I have tested this on emulators of Android 7 and 6 (API 24 and 23).
I have tried:
clearing the app's data cache and wiping the device, so it's fresh
definitely not this Getting permission denied on Android M
triple-checked spelling after seeing this onRequestPermissionsResult returns immediately with denied permission and I am calling the super method
a bunch of other suggestions on stack overflow
I'm pretty stumped...
Here is the permission request (see comment in the code):
private fun askForPermissionOrSendRequest(view: View, permission: String) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED) {
if (shouldShowRequestPermissionRationale(permission)) {
cachedView = view
val explanationDialog = AlertDialog.Builder(this).setMessage("We need permissions to read your storage in order to show your profile image.").setOnDismissListener {
requestPermissions(
arrayOf(permission),
BSMainActivity.permissionRequestSendProfilePic
)
}.create()
explanationDialog.show()
} else {
cachedView = view
// this branch is always hit - the permission seems to be missing every time
requestPermissions(
arrayOf(permission),
BSMainActivity.permissionRequestSendProfilePic
)
}
} else {
sendRequest(view)
}
}
I immediately get to the result handler without a dialog showing up to ask me for permissions. I may or may not have confirmed the same permission in another (child) activity of the same app (doesn't seem to make a difference).
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
BSMainActivity.permissionRequestSendProfilePic -> {
// This gets hit, MANAGE_DOCUMENTS was denied
if (permissions.contains(Manifest.permission.MANAGE_DOCUMENTS) && grantResults[permissions.indexOf(Manifest.permission.MANAGE_DOCUMENTS)] == PackageManager.PERMISSION_DENIED) {
Log.w(logName, "Permission to open image was denied while sending a tag request: %s %s".format(
permissions.joinToString(",", "[", "]"),
grantResults.joinToString(",", "[", "]")
))
}
// send request regardless of the result for now
sendRequest(cachedView)
}
}
}
In my manifest I have the following:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.monomon.bs">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS"/>
Only dangerous permissions can be requested at runtime and MANAGE_DOCUMENTS is not a dangerous permission.
As per the MANAGE_DOCUMENTS documentation:
This permission should only be requested by the platform document management app. This permission cannot be granted to third-party apps.