From recording videos, to making phone calls, to completing purchases and posting updates to your various social media accounts – there's countless legitimate reasons why an Android app might require access to sensitive services, device capabilities, or user information.
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
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:
- Verify that your app is installed on a device that supports the runtime permissions model.
- Check whether your app has access to the permission in question.
- Trigger Android's permission request dialog.
- Process the user's response.
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:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://ift.tt/nIICcg" xmlns:app="http://ift.tt/GEGVYd" xmlns:tools="http://ift.tt/LrGmb4" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.jessicathornsby.permissions.MainActivity"> <Button android:id="@+id/button" 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:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://ift.tt/nIICcg" 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:
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:
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.
@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.
Note that you can also download the finished project from GitHub.
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:
- Launch the 'Settings' app.
- Select 'Applications.'
- Select 'All Apps' from the dropdown menu.
- Scroll through the list of apps until you find the app in question, and give it a tap.
- Select 'Permissions' from the subsequent menu.
- This screen displays all the permissions that this app may request. You can toggle each of these app permissions on and off, using the accompanying slider.
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:
- Provide additional information. Ideally, you should time each request so that it's obvious why your app requires this particular permission, but if you suspect users might be unclear about why your app is trying to access this feature or information, then you should provide some additional information by calling shouldShowRequestPermissionRationale before requestPermissions:
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();
- Change when you issue this request. The more invested a user is in performing an action, the more likely they are to hit that 'Allow' button. For example, if your app includes a route planning feature, then users are more likely to grant your app access to their location after they've typed out their destination and tapped 'Display my route,' compared to when they first select 'Route planner' from your app's main menu.
- Stop requesting this permission. If a permission is related to a non-essential feature, then you may want to consider removing it from your app, to avoid the user having the negative experience of denying your app access to the information or features that it's requesting.
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."
<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:
<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!
from Android Authority http://ift.tt/1WmQqDD
via IFTTT
Aucun commentaire:
Enregistrer un commentaire