In the previous video, we got the app running on Android versions earlier than Marshmallow, which is API
23. With API 23,
Google changed the way that permissions work, and we looked at that briefly in the top 10 Downloader app.
So I'm going to run the app again, but this time I've loaded the API 27 emulator that I set up in an earlier video in this
section of the course, the one that we've also entered some contact details into.
So this time when I run the app,
so the app starts as per normal, but when I click on the floating action button we actually get an error
here:
Content Provider Example has stopped. And if we come and have a look at our logcat, we can
see that we've got an exception. I'll just go back up and look at it. Now we can see it here, the error, Permission Denial.
Basically it goes on to say the Permission Denial was for opening provider com.android.providers
dot contacts.ContactsProvider2.
The bottom line here is with API 23 and above, it's not enough to just request the permissions we need
in the manifest.
We also need to write some code to request the permissions from the user when the app runs.
So let's check the Google documentation here. So there's a guide which is pretty useful about Permissions, Android
Permissions. I'm just going to paste in the link there.
Now as it turns out, this documentation has been updated since we looked at the top 10 app, and the earlier link now
redirects to here. So I'm going to start with this Permissions Overview at the top of the page. But do have
a read through this guide
once you've finished this section, to make sure you understand how everything works.
We already know that we have to specify the permission in the manifest, regardless of the version of
Android.
And we've already done that step
when we added the user's Permission tag in the Android manifest.xml file.
But another important thing to note is that, in that first paragraph, is that the system might grant
the permission automatically.
So that's what happened in our top 10 app, because the Internet permission we needed is classed as a normal
permission.
So what that means is we didn't have to do anything else to be granted permission to access the Internet.
But accessing the user's contacts is classed as a dangerous permission,
though, so Android needs to ask the user to approve that permission.
Now we didn't really see that when we ran our app on the Lollipop emulator.
That's because the method that Android Studio uses to install apps on the device, bypasses the checks
that you see when you install an app from the Google Play Store.
Now earlier in the course, we enabled USB debugging on our phones, and you should have seen a dialogue
pop up asking for confirmation that looks something like this:
Now notice that it does say that enabling USB debugging can install apps on your device without notification.
The emulators have USB debugging turned on by default, and that automatically accepts the permissions
that you'd normally have to accept when installing a new app.
So although we didn't see it when running our apps from Android Studio, you'd normally have to accept
the permission that the app requested when you install it.
And that's how it worked before Marshmallow.
But when Google released Marshmallow they changed the way that permissions work.
So let's go back to that web page now, just to see what this all means for Marshmallow and above.
So we can get a good description of the change by scrolling down to the Requests prompts for dangerous
permissions section,
to this section here.
So that confirms what we found in the top 10 app: Only dangerous permissions require user agreement.
Now the next subsection there describes the differences from the user's perspective.
If the device is running Android 6.0 API Level 23 or higher, users grant permission to apps while the
app is running,
not when they install the app.
There's also an important comment there about the target SDK version.
The target SDK version has to be 23 or higher for this behavior.
If you target a lower version, then your app will have the earlier behavior regardless of the device that
it's running on.
The user will be prompted to grant access when they install the app.
It also states that you can't always rely on having a permission. The user may grant it, but then deny
it again later.
And if you think about it that obviously has implications for us as developers, because we now have to
write our apps so that they can handle the situation where a permission is no longer granted.
Now for some apps that may involve us disabling some functionality. An example could be a photo
processing app that allows the user to share the modified photo with their contacts.
If the user revokes the apps permission to access their contacts, then the app would be unable to share
the photo.
So in that case we shouldn't even display that option at all. The app would still be able to perform its photo
manipulation functions but would be unable to share the result. Now in the case of our simple contacts
app,
if the user doesn't grant access to the contacts database, then the entire app will become useless because
that's all it does.
So the next section, if we scroll down a little bit more, talks about Install, Install-time requests. And by the way I didn't scroll down,
but that other information that I was talking about related to Android 6.0 and higher is on that page there - you
can see it talks more about not relying on always having the permission. But scrolling down further now
we're talking about the Install-time requests Android 5.1.1 and below.
That's describing the behavior on earlier versions of Android, and that also applies if your target is
SDK version is 22 or lower, regardless of the version of Android that the device is running.
So in that case the permissions are requested when the app is installed.
The user can either grant all permissions or decide not to install the app,
and talks more about that as
we scroll down. If the permissions are granted,
the only way to deny any of those permissions is to uninstall the app. So going back up the page now,
just down here to the end of the Permission approval section,
there's a link there to the protection levels - this link here. Now that lists the normal and dangerous
permissions, and lets you check if you need to do more than just add an entry in the manifest for a specific
permission or permissions that your apps need. Click on that and have a look,
but I'll leave that for now.
We'll go back to Android
Studio, and let's now look at the code we need to check for those permissions, and then we'll consider what
to do if they're not granted.
But what I'm going to do first is start by adding a constant and a variable that we'll be using shortly.
So I'm going to do that. I'm going to close down the logcat window, go up to the top of the file. So lay our constant below our TAG, so it's going
to be private
const Val. We'll call this one REQUEST, in
uppercase, underscore CODE underscore READ underscore CONTACTS, and we'll assign the value of 1 to that, equals 1.
And we're using that variable shortly.
I'll also explain what it's for when we come to using it, but also in the class itself
let's add a variable, and we're going to call it
readGranted. So it's going to be private, private var
and it's going to be read
Granted equals false.
Same deal, I'll talk about that shortly,
and obviously in the case of the constant on line 14, you saw that I made it a top level value using the
private const modifiers.
Alright, so the first step now is to check to see if the app has already been granted the permission
it needs.
And we can do that by calling the checkSelfPermission method. So I'm going to add the code and then say a
couple of things about it.
So I want to put that code just immediately below this
setSupportActionBar call.
And it's going to be val hasReadContactPermission,
and we'll set that equal to ContextCompat
dot, and it's going to be check
SelfPermission. In parentheses it's going to be this comma, and it's going to be Manifest. At this point here
it's important to choose the android.Manifest package, this one down here and not the java.util one. So Manifest dot
permission
dot READ_CONTACT. And what I'm going to do is just put that on the next line. Actually I won't, it reads
alright.
I just wanted to make sure there's enough space on the screen to see that, but we can see that.
So we're checking to see whether we've got that permission and
I'm just going to do a log entry here - Log.d parentheses TAG comma.
Then in double quotes onCreate colon check
SelfPermission
returned $hasReadContactPermission.
Now because the new security model was introduced in API 23 and we're targeting Android versions way
back to API 17, we have to use the v4.support library to get access to the checkSelfPermission
method.
Now there is a checkSelfPermission method in the framework, but that would only work for Marshmallow
and above.
And both methods return the same result, but the ContextCompat method which we selected, first checks
to see if it's running on Android prior to API 23. If it is it just a little success,
and that's of course because the new security model doesn't apply before API 23.
Now remember that we defined the permissions we needed in the manifest,
so therefore we can use the constants defined in the manifest's class to specify which permission we're
checking.
Here it's the READ_CONTACTS permission. So having to type Manifest.permission.READ
underscore CONTACTS
everytime is a bit of a mouthful,
so we can import the constant from android.Manifest.permission instead. Now Android Studio add the import
for us,
so first note that android.Manifest has been imported to give us access to the Manifest.permission
dot READ_CONTACTS field.
So if we have a look in the import section, you can see here, android.Manifest. In my case, it's literally
the first entry of the import section of the source code. The ordering of course isn't important,
so it may well be appearing lower down for you.
So if I go back and remove the Manifest.permission qualifier from the READ_CONTACTS field -
I'll just make that READ_CONTACTS,
and what I can do is just delete that if needed. You can see the old one's still there but it has actually added
the new ones.
I'm just going to delete the old one.
But what it should do then, as you can see, it added the android.Manifest.permission.READ
underscore CONTACTS.
So therefore we can now get away with just putting READ_CONTACTS here.
Let me just take this opportunity to check my preferences here. I'm going into Preferences, going into Editor,
General, Auto import, and I've got Add unambiguous imports on the fly and Optimize imports on the fly
which is what that uses.
So I'm not sure why that other
grey entry appeared and appeared to sit there for a while but it should be automatic, and remove that
automatically for me.
But anyway, at this point we've actually succeeded now.
We've got a the line making it a lot easier to read now, using just READ_CONTACTS rather than the qualifier
that we had previously.
So at this point though, looking at this import on line 3,
that's the equivalent of what's called a static import in java.
Now if you're not familiar with the static imports in Java, that's just a way to import the static members
of a class,
and these are usually constants and it saves having to fully qualify them.
Kotlin doesn't have the static keyword, but importing the fully qualified value like
this is the Kotlin equivalent. Importing like this can be useful, but use it sparingly. If you over
do it
you can make your code hard to understand and debug.
Now to see what I mean, have a look at the code - coming down here again -
from line 27 onwards. And if you look at that code from line 27 onwards, it's now not immediately obvious where READ
underscore CONTACTS is coming from, and if it had also imported the ContactsContract constants, we'd have
a lot of constants coming from different classes without being qualified.
And that can make it hard to see what's going on and where all these constants are coming from.
So I recommend not using this type of import for more than one class at a time.
Alright so back to line 27.
So we've passed a context, this here, as the first parameter,
and we also passed a constant representing the permission we're interested in to the checkSelf
Permission method.
Now we'll get back an int result.
So let's check the documentation to see what the return value is.
So I'm going to click on the checkSelfPermission method and bring up the documentation,
and you can see that these return values are fairly straightforward.
We get one of the two constants returned; either PERMISSION_GRANTED if we already have the permission,
or PERMISSION_DENIED
if we don't. Now on Android versions before Marshmallow, we should always get PERMISSION_GRANTED as long as
we specify the permission in the manifest. On Marshmallow and above, the permission won't have been
granted when the app was installed,
and that means that the first time we run this app, we'll get PERMISSION_DENIED and we'll
have to request the permission from the user.
This is where things get a bit interesting. Now it would be fairly straightforward except that
users have an option to deny a permission, and you can tick a box so that they won't get asked again.
I'm going to have a look at how that works and how to deal with it in the next video.