I started learning about how I can request app permissions from the Google Documentation. This is basically the code that I'm trying to execute.
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
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.
}
}
Unfortunately, whenever I'm trying to write the registerForActivityResult(...) part, Android Studio keeps highlighting it as an error.
There's another step that I need to perform along with this. It tells me to add this dependency. Even after doing that, Android Studio still shows me that it shows an error. Can anyone tell me why this happens?
You're missing the following
// Kotlin
implementation 'androidx.activity:activity-ktx:1.2.0-beta01'
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
Related
I need to publish my flutter application on the playstore but I received several rejections. My application is used to identify the caller using my database.
I think my problem is that I don't know how to ask permission to become the default application for spam detection. Does anyone have the answer to this?
I've tried to change the permissions i asked in the android manifest, my last version is this :
\<uses-permission android:name="android.permission.READ_PHONE_STATE"/\> \<uses-permission android:name="android.permission.READ_CALL_LOG"/\> \<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/\>
Thank you very much for your help !
In order to screen calls correctly should extend the CallScreeningService class:
class MyCallScreeningService : CallScreeningService() {
override fun onScreenCall(details: Call.Details) {
val callResponse = when {
// Perform checks to determine if the call should be blocked or allowed.
// Return a new CallResponse object with the appropriate response action.
// e.g. CallResponse.reject() to block the call, CallResponse.allow() to allow the call.
else -> null // Return null if the call should be allowed.
}
respondToCall(details, callResponse) // Send the call response to the system.
}
}
You'll still need to register your CallScreeningService implementation in your AndroidManifest.xml file for it to be used by the system. Also, you will need to request the necessary permissions to access call details and control call responses.
Here's a more detailed article about this subject
How can I programmatically determine whether the "Remove permissions if app is unused" setting is enabled or disabled for a particular app?
You can check whether the user has enabled or not, and you can also request them to disable it.
Check if the user has it enabled:
val future: ListenableFuture<Int> =
PackageManagerCompat.getUnusedAppRestrictionsStatus(context)
future.addListener(
{ onResult(future.get()) },
ContextCompat.getMainExecutor(context)
)
fun onResult(appRestrictionsStatus: Int) {
when (appRestrictionsStatus) {
// Status could not be fetched. Check logs for details.
ERROR -> { }
// Restrictions do not apply to your app on this device.
FEATURE_NOT_AVAILABLE -> { }
// Restrictions have been disabled by the user for your app.
DISABLED -> { }
// If the user doesn't start your app for months, its permissions
// will be revoked and/or it will be hibernated.
// See the API_* constants for details.
API_30_BACKPORT, API_30, API_31 ->
handleRestrictions(appRestrictionsStatus)
}
}
ask to disable it:
fun handleRestrictions(appRestrictionsStatus: Int) {
// If your app works primarily in the background, you can ask the user
// to disable these restrictions. Check if you have already asked the
// user to disable these restrictions. If not, you can show a message to
// the user explaining why permission auto-reset and Hibernation should be
// disabled. Tell them that they will now be redirected to a page where
// they can disable these features.
Intent intent = IntentCompat.createManageUnusedAppRestrictionsIntent
(context, packageName)
// Must use startActivityForResult(), not startActivity(), even if
// you don't use the result code returned in onActivityResult().
startActivityForResult(intent, REQUEST_CODE)
}
Source: https://android-developers.googleblog.com/2021/09/making-permissions-auto-reset-available.html
That's a great question and I'm still trying to determine what that even means.
It appears on my Bixby app that came installed on my Samsung. It goes off at random at least 4 times an hour.
I've disabled it many times and I feel "remove permissions if app is unused" is worded in such a confusing way intentionally with the intention to be invasive.
I'm developing a flutter application, I need to manage the permission request, but I don't know how to treat a particular occurrence:
If I deny two times the same permission through the popup it could be impossible approve it later 'cause the popup will not appair again.
Future<void> requestStoragePermission() async{
var status = await Permission.storage.status;
if(status.isPermanentlyDenied){
await AppSettings.openAppSettings();
} else {
await Permission.storage.request();
}
}
I don't understand how to distinguish when the permission has not yet been granted or when it has been refused several times because the function: Permission.storage.status always returns "denied".
****** EDIT ******
The problem arises when the user refuses the same permission several times (2 times) because the permissions request popup is no longer shown, in which case it is necessary to manually open the application settings and modify the permissions by hand. I have to make sure that: the first two times I request permissions with the popup then I should open the settings screen
I have always managed my permissions using two statuses granted and limited (used only for iOS14+). These two permissions are the only truethy statuses. All the others are falsey statuses.
the permission_handler package handles a lot of logic for you already. Before it makes the request, it will check the status to see if it is already defined. If it is, then it will return the status. If the permission has never been requested, then it will request the permission.
Personally, I set up a generic method for a permission request, to keep things DRY.
Future<bool> requestPermission(Permission setting) async {
// setting.request() will return the status ALWAYS
// if setting is already requested, it will return the status
final _result = await setting.request();
switch (_result) {
case PermissionStatus.granted:
case PermissionStatus.limited:
return true;
case PermissionStatus.denied:
case PermissionStatus.restricted:
case PermissionStatus.permanentlyDenied:
return false;
}
}
I then make a request like
final canUseStorage = await requestPermission(Permission.storage);
if (canUseStorage) {
// do something with storage
}
If you have UI that is dependent on a status from Permission, then you still call Permission.storage.status.
[EDIT]
At the moment, you can't track how many times the request pop-up has been shown via permission_handler. It only returns the status. You would need to take the user to the settings depending on the returned status value.
Side Note
Instead of taking the user directly to the settings. Maybe you show a pop up saying "Looks like we don't have permission...", with a button that the user can tap to go to the settings, provides the user with some context as to why they need to go to their settings. And it's also a better user experience!
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 am developing a simple app using Flutter's location plugin, with some code based on their sample code:
var location = new Location();
try {
_currentLocation = await location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
_locationMsg = 'Permission denied';
}
_currentLocation = null;
}
As indicated in the plugin page, I added ACCESS_FINE_LOCATION to the Android manifest.
The problem is, when I test the app on a phone (with Android 9, in case it's relevant), even though location is enabled and I have a GPS signal, executing the above code results in the following prompt:
The prompt reads: "For a better experience, turn on device location, which uses Google's location service.", with two buttons: "NO THANKS" and "OK".
This is horribly user-unfriendly: location is already coming from the GPS, there is no need to further bother the user.
Where is the problem coming from, and how can I avoid that prompt? I prefer reporting "unknown location" than having the prompt displayed.
Edit: Note that the prompt is not related to notifying the user that the location will be used, but it is a Google privacy-invading feature that, when you click OK, enables Google Location Accuracy, as described below (hidden deep in a Settings menu):
The above image reads: "Improve Location Accuracy", with a toggle button. Google Location Accuracy: Google’s location service improves location accuracy by using Wi‑Fi and mobile networks to help estimate your location. Anonymous location data will be sent to Google when your device is on.
Clicking on the first prompt enables this, which the user then has to manually disable if they don't want to send their location data to Google. Disabling it and trying to get the location again results in the same prompt, so it is definitely not related to warning the user about the usage of location data. Also, if Google Location Accuracy is enabled before using the app, the prompt never appears in the first place, which is probably why most developers never notice it.
I know it is possible to get location data without enabling Google Location Accuracy, since most apps do it. But I don't know where the prompt comes from: is it Flutter's location plugin? The fact that I am using an Android 9 SDK? Or the sample code?
It seems the issue is coming from the location plugin. I tried replacing it with geolocator, and modifying the caller code, and this time no such prompt appears.
I tried lowering the accuracy before asking for location, but the location plugin still displays the prompt. There must be some underlying code which is hardwired to request Google Location Accuracy in all cases.
If only Google would provide a way to permanently disable the prompt with a systematic
"no" (this has been an issue for several Android releases), I might have given them the benefit of doubt.
I was having this same issue today but solved it by locating the offending expression and added an if statement to check whether the app's location permission was granted.
Here is my code in Kotlin and the offending expression is under the note in all caps:
private fun checkLocationPermission() : Boolean = (ContextCompat.checkSelfPermission(
requireContext(), ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
private fun startLocationRequests() {
val locationRequest = LocationRequest.create()?.apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest!!)
val client: SettingsClient = LocationServices.getSettingsClient(requireActivity())
val task: Task<LocationSettingsResponse> = client.checkLocationSettings(builder.build())
task.addOnSuccessListener { locationSettingsResponse ->
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
if (checkLocationPermission()) {
locationPermissionGranted = true
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper())
}
}
task.addOnFailureListener { exception ->
if (exception is ResolvableApiException){
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
// IF STATEMENT THAT PREVENTS THE DIALOG FROM PROMPTING.
if (checkLocationPermission()) {
exception.startResolutionForResult(
requireActivity(),
REQUEST_CHECK_SETTINGS
)
}
} catch (sendEx: IntentSender.SendIntentException) {
// Ignore the error.
}
}
Timber.i("Location Listener failed")
}
}
Now my solution may not exactly apply to your problem but I think it should be enough to learn from to solve your problem. However, you may need to redo your code when it comes to requesting a user's location, though.
Also, I'm not so sure about using Flutter's plugin for location but in your case and others, I would recommend following the developer documentation when it comes to requesting a user's location. Perhaps it's applicable to Flutter too:
https://developer.android.com/training/location/change-location-settings