I’m pretty sure that you know that with Marshmallow (SDK 23), we got a new permissions system. Permissions are not handled by the apk installer anymore. Before Android 6.0, all permissions were shown on the screen after clicking “Install” on any application on Google Play. Since Android 6.0, the permissions are handled at the runtime. What it means is that user is asked to Allow or Deny permissions after he runs the application.
I’m not entirely sure why Google made this decision, if I were to guess I’d say that they wanted to provide users with more flexibility. Imagine that you got an application Calculator, which requires you to allow Location services, so the app can collect your location. The developers might need it, so they know where people use their Calculator app, but for you it is absurd to allow this, as all you care about is calculations and it does not make any sense to send your location information while calculating. That is where the new permission system comes handy; you can simply deny the location permission and go ahead with your calculations. This is just an example; there might be more of a background story to this system.
Although everything is nice and fair now, it is harder for developers to implement all the situations that could possibly happen. What if user denies a permission that you strictly need for you app to work properly? And what if they check the ‘Don’t show again’ option and you won’t be able to ask them about the permission again? These are the consequences you have to expect. We will be implementing a permission check that will work based on this diagram:
There are many libraries that make it easier to implement these permissions. Using a library can sometimes cut off a piece of flexibility for you though, and it also makes the release APK a bit bigger. It is absolutely easy to implement the permissions using native Android classes, that is why I will show you how to do it in the simplest way. There are two options here – you can either require permissions on application startup, or during it’s lifecycle, f.e. on click on a button that further performs a task which requires some permission.
I will implement the second option, but it’s absolutely easy to transform it to the first option too, so you can choose what fits your application better. So let’s say we have a button that writes a file into phone’s storage. We will create an onClickListener for it and inside we will check if the system has the required permission.
btn_write.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Comment 1
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
.... write file into storage ...
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, // Comment 2
666);
}
}
}
});
Comments:
1: As I said, permissions before version 6.0 (Build.VERSION_CODES.M) are granted during application installation. Method checkSelfPermission works only since version 6.0, that is why you have to perform this check. Without it, you would get an error
2: Method requestPermissions accepts an array of String objects as a parameter. You can add more permissions here, just add them into new String[] { }
When the requestPermissions() method is fired off and the user clicks on Allow or Deny on the permission dialog, you can catch his action and proceed with it by overriding the onRequestPermissionsResult() method. So when the user allowed the permission WRITE_EXTERNAL_STORAGE, we can go further with our code and write the file into storage. Although if he denied the permissions, there is no way we can continue. If he really denied the permission, we will show a Snackbar saying that this app will not run properly without this permissions and we will also show him a button that will forward him into Settings where he can allow this permissions.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case 666: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Comment 1.
... we got the permission allowed, now we can try to write the file into storage again ...
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) { // Comment 2.
Snackbar s = Snackbar.make(findViewById(android.R.id.content),"We require a write permission. Please allow it in Settings.",Snackbar.LENGTH_INDEFINITE)
.setAction("SETTINGS", new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 1000); // Comment 3.
}
});
View snackbarView = s.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(3); // Comment 4.
s.show();
}
}
}
}
}
Comments:
1: In our case, we request only one permission, hence grantResults[0]. If you require more of them, you should loop through this array and check every one of them.
2: There is a different explanation for shouldShowRequestPermissionRationale() method, but I will give you a different one. This method returns false if the user ticked the ‘Don’t show again’ option. As long as the user did not tick the ‘Don’t show again’ option, the method tells us that we still can and should show the dialog requesting the specific permission – method returns true. After user ticked it, we can no longer show the dialog – method returns false. We do this check, because while user hasn’t ticked the option, we do not have to show any Snackbar. The user can just go on and click the btn_write until he realizes that he needs to tick the option if he wants to get rid of the dialog. After he does tick it, then we show him the Snackbar which will forward him to options.
3: The number ‘1000’ is just an example. You need this number to differentiate between separate activity results. This activity redirects user to options. After he comes back from options, we will want to know that he did, so we will check if the result of the intent is also 1000 and if is, we will check again if he allowed the permission there.
4: This is a simple trick. Our Snackbar grows on size based on length of the chosen text. This command will allow it to display more lines than just one.
Now that we have this done, the only thing left is to check the permission again when user returns from Settings. It’s fairly easy – if he did allow the permission, we will not do anything, he can continue using the application without any restrictions. Although if he did NOT allow it (meaning he went to Settings but then he came back without changing anything), we have to show him the same Snackbar again. In the previous step, we started the settings intent with code 1000, so we will catch the same code in overridden onActivityResult() method which handles returns from other activites.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1000 && resultCode == Activity.RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
Snackbar s = Snackbar.make(findViewById(android.R.id.content),"We require a write permission. Please allow it in Settings.",Snackbar.LENGTH_INDEFINITE)
.setAction("SETTINGS", new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 1000);
}
});
View snackbarView = s.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(6);
s.show();
}
}
}
In the code above we check if the permission was granted, we don’t do anything, although if it is not granted, we show the same Snackbar. And that’s ALL! We have a very simple circular permission check cycle that will not fail in any case and will make sure that user will grant the permission. If you do not understand any