What are Android App permissions, and how do devs implement them?

Publish date: 2022-07-30

However, exploiting access to sensitive data and device features is also a favourite technique of malicious apps, so to help keep users and their devices safe, Android apps run in a limited access sandbox by default. If your app requires access to anything that isn’t included in this basic sandbox, then it needs to ask for the user’s permission.

How to write your first Android game in Java

Android Development

Pre-Marshmallow, you just needed to declare each permission in your Manifest, and the user would then be asked to approve all of these app permissions at install time. However, Android 6.0 replaced this all-or-nothing approach to app permissions with a more flexible runtime permissions model. As of API 23, you need to declare all of the permissions your app might require in your Manifest, but you also need to request each permissions at runtime, if and when your app needs to perform a task that requires this particular permission. The user can then choose to grant the permission request, or deny it – something that wasn’t previously possible.

In this article I’m going to show you how to implement runtime permissions, by creating an app that demonstrates the entire runtime permissions model in action. By the end of this article, you’ll have all the code you need to:

Create your layout

One of the biggest benefits of runtime permissions is that they allow you to request app permissions in context, typically when the user is trying to complete a task that requires this permission, which has the potential to remove a lot of the confusion and uncertainty surrounding why your app requires a particular permission.

To demonstrate this in action, we’re going to create an app that consists of an ‘Upload Photos’ button; every time the user taps this button our app will check whether it has access to the device’s external storage and, if it doesn’t, it’ll issue a permission request.

Create a new Android Studio project that’s capable of running on Android 6.0 or higher, and let’s create our layout:

Code

Copy Text
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:andro xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.jessicathornsby.permissions.MainActivity"> <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Upload photos" /> </android.support.constraint.ConstraintLayout>

Declaring app permissions in the Manifest

The new app permissions mode still requires you to declare all of the permissions your app might request, so open your Manifest and add the READ_EXTERNAL_STORAGE permission:

Code

Copy Text
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:andro package="com.jessicathornsby.permissions"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Does your app already have permission?

Every time the user taps the ‘Upload Photos’ button, we need to check whether our app is installed on a device that’s running Android 6.0 or higher, and whether it has access to the READ_EXTERNAL_STORAGE permission.

You need to check the latter every single time the user taps the ‘Upload Photos’ button, as Android 6.0 and higher gives users the ability to revoke a previously-granted permission at any time, via their device’s ‘Settings’ app. Even if your app previously had access to this permission, there’s no guarantee that the user hasn’t revoked this permission since the last time you checked.

Open your MainActivity.java file, and add the following:

Code

Copy Text
public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 1; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button= (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Check whether the app is installed on Android 6.0 or higher// if (Build.VERSION.SDK_INT >= 23) { //Check whether your app has access to the READ permission// if (checkPermission()) { //If your app has access to the device’s storage, then print the following message to Android Studio’s Logcat// Log.e("permission", "Permission already granted."); } else { //If your app doesn’t have permission to access external storage, then call requestPermission// requestPermission(); } } } });   } private boolean checkPermission() { //Check for READ_EXTERNAL_STORAGE access, using ContextCompat.checkSelfPermission()// int result = ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE); //If the app does have this permission, then return true// if (result == PackageManager.PERMISSION_GRANTED) { return true; } else { //If the app doesn’t have this permission, then return false// return false; } }

If checkPermission returns false, then your app doesn’t currently have access to the device’s external storage, and you’ll need to request this permission from the user.

Display the permission dialog

You request a permission by calling the ActivityCompat.requestPermissions method:

Code

Copy Text
private void requestPermission() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); }

Your app will then display the standard permission dialog, and the user will have the option to accept or deny this request.

Handling the user’s response

Once the user responds to the permission request dialog, your app will receive a callback with the results – either PERMISSION_GRANTED or PERMISSION_DENIED

To process these results, you’ll need to implement ActivityCompat.OnRequestPermissionsResultCallback; the results of permission requests will be delivered to its onRequestPermissionsResult(int, String[], int[]) method.

Since this is just a sample app, accepting or denying the permission request won’t have any noticeable impact on the user experience, so I’m using two toasts to provide a visual indication that the app has properly registered the user’s response.

Code

Copy Text
@Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_CODE: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity.this, "Permission accepted", Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, "Permission denied", Toast.LENGTH_LONG).show(); } break; } } }

And that’s it! Attach a compatible Android Virtual Device (AVD) or physical Android device to your development machine, install and launch your app, and then give the ‘Upload Photos’ button a tap.

Regardless of whether you tap ‘Accept’ or ‘Deny,’ your app should display a toast confirming that the system has registered your response.

When you’re testing your own apps, you’ll want to check how your app functions when the user accepts and denies each of your permission requests. On Android 6.0 and higher you can quickly toggle individual app permissions on and off, via your device’s ‘Settings,’ which can be invaluable during testing:

Best practices for runtime permissions

Now we’ve covered how to implement runtime permissions, let’s look at how to implement them effectively, by covering some of the best practices that are unique to the runtime permissions model.

Limit your permission requests

The whole point of Android’s permission-based system is to help keep users safe from malicious apps that might try to steal or corrupt their data, or damage their device. While it’s not unusual for an app to request multiple permissions, every time your app triggers the permission request dialog you’re essentially prompting the user to question whether they really trust your app, so it’s crucial that you only request app permissions that your app cannot function without.

If you’re concerned about the number of permissions your app requires, then keep in mind that your app only needs to request permission for tasks that it performs directly. Depending on what you’re trying to accomplish, you may be able to achieve the same results by asking another app to do all the hard work for you, for example instead of requesting the android.permission.CAMERA permission, you could launch a camera app that the user has already installed on their device.

You should also pay attention to when you issue each permission request. In particular you should avoid issuing multiple requests in quick succession, as this is almost guaranteed to make your users doubt your app, and wonder whether it’s just going to keep pushing for more and more access to their personal information and device capabilities.

Spend some time mapping out all the different paths users are likely to take through your app, and then pinpoint where they’ll encounter each request on these different paths. If you do spot any paths where a user is likely to encounter multiple requests in a short period of time, then you should try to create some space between these requests, for example by changing your app’s navigation, tweaking the UI, or changing the order these screens appear in.

Make it easy for users to grant and revoke app permissions

We’ve already seen how Android users can manually change an app permissions via their device’s ‘Settings.’ This allows them to revoke previously-granted permissions, and can also come in handy if a user denies a permission request, then realizes they need to reverse this decision asap after seeing how it’s impacted the user experience.

However, this part of the ‘Settings’ menu becomes particularly important if a user ever selects ‘Don’t ask again’ from the permission dialog, before hitting ‘Deny.’

Once this option is selected, every time your app calls the requestPermissions() method the system will call onRequestPermissionsResult() and pass it PERMISSION_DENIED automatically, without ever displaying the permission dialog.

This isn’t a problem for a user who’s aware that they can always change an app’s permissions via their device’s ‘Settings,’ but there’s no guarantee that everyone who installs your app will be aware of this fact. Plus, even if they’re familiar with this section of the ‘Settings’ menu, reaching it requires navigating through several different screens – not exactly a great user experience!

Ideally, you should provide a way for users to change all of your app permissions, as and when they require, without them having to leave your app, for example you might want to consider adding a ‘Permissions’ option to your app’s main menu.

Refine problematic permission requests

Users deny permission requests because they’re uncomfortable, suspicious or confused about why your app requires access to this part of their device. Since users on Android 6.0 and higher can deny individual permissions, this gives you the opportunity to monitor how users are responding to each of your app’s permission requests.

If a large number of users are denying the same request, then this is a sign that you may need to rethink this particular permission:

Code

Copy Text
private void requestPermission() { … … ... if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(MainActivity.this, "This permission is required to view and upload the photos stored on your device.", Toast.LENGTH_LONG).show();

Don’t forget about <uses-feature>

If your app relies on certain hardware being present on the user’s device, then you need to instruct Google Play to prevent anyone from downloading your app to a device that’s lacking this essential hardware. You do this by declaring the feature in your Manifest and setting it to “true.”

Code

Copy Text
<uses-feature android:name="android.hardware.camera" android:required="true"/>

Google Play also checks your app’s permissions for any implicit feature requirements. If it discovers that your app uses one or more of these permissions but doesn’t declare the related feature via a <uses-feature> element, then it’ll assume that this hardware is required for your app to run (android:required=”true”). If your app uses this feature where available but can function with it, then this behaviour can prevent your app from being installed on perfectly compatible devices.

If any of your permissions imply a feature that your app doesn’t require, then it’s crucial that you declare this feature in your Manifest and mark it as android:required=”false.” For example:

Code

Copy Text
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />

Wrapping Up

Do you have any advice or best practices for getting the most out of Android 6.0’s runtime permissions model? Let us know in the comments section!

Comments

ncG1vNJzZmivp6x7orrDq6ainJGqwam70aKrsmaTpLpwrc2dqaihlGKusbyMqZyrpZmowKq7zaxknrCgoa6qusSdZG9sYmmCc3s%3D