Asking for Permission at Runtime

Handling permissions at runtime requires you to do three things:

  • check to see whether you have permission before performing the operation

  • request permission if you do not already have it

  • listen for the response to your permissions request

Requesting permission means displaying the system-standard permissions request UI. If this dialog pops up in a place in your app where it is clear why you are asking your user for this permission, then this is all you need to do.

Sometimes, though, the reason is not obvious. Users often deny requests that do not make sense to them, so in those cases you will need to display a rationale. So those apps require a fourth step:

  • check to see whether you need to display a rationale to the user

Since Locatr’s mission is on the obvious side, you will not implement a rationale in this exercise – at least, not until the challenge at the end of this chapter.

Checking for permissions

Your first step will be to pull your permissions information into your Java code. You can do this by adding a constant array to the top of LocatrFragment that lists all the permissions you need.

Listing 33.17  Adding permissions constants (LocatrFragment.java)

public class LocatrFragment extends Fragment {
    private static final String TAG = "LocatrFragment";
    private static final String[] LOCATION_PERMISSIONS = new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
    };

    private ImageView mImageView;
    private GoogleApiClient mClient;

All standard Android permissions are declared in the Manifest.permission class for you to use programmatically. So these two constants refer to the same string values you used in your AndroidManifest.xml. With your permissions array declared, your next step is to ask for the permissions you need.

Permission is granted for dangerous permissions on the basis of permission groups, not individual permissions. A permission group contains several permissions that deal with the same kinds of access. For example, ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION are both in the LOCATION permission group.

Table 33.1  Permission groups

Permission group Permissions

CALENDAR

READ_CALENDAR, WRITE_CALENDAR

CAMERA

CAMERA

CONTACTS

READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS

LOCATION

ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION

MICROPHONE

RECORD_AUDIO

PHONE

READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG, WRITE_CALL_LOG, ADD_VOICEMAIL, USE_SIP, PROCESS_OUTGOING_CALLS

SENSORS

BODY_SENSORS

SMS

SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS

STORAGE

READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE

Once a permission is granted for any permission within the group, permission is granted for all permissions in the group. Since both of your permissions are in the LOCATION group, you only need to check whether you have one of those permissions.

Write a method to check whether you have access to the first permission in your LOCATION_PERMISSIONS array.

Listing 33.18  Writing a permission check (LocatrFragment.java)

    private void findImage() {
        ...
    }

    private boolean hasLocationPermission() {
        int result = ContextCompat
                .checkSelfPermission(getActivity(), LOCATION_PERMISSIONS[0]);
        return result == PackageManager.PERMISSION_GRANTED;
    }
}

Because checkSelfPermission(…) is a relatively new method on Activity, introduced in Marshmallow, you use ContextCompat’s checkSelfPermission(…) instead to avoid ugly conditional code. It will take care of the compatibility behavior for you.

Next, add a check before calling findImage() to make sure you have the permission.

Listing 33.19  Adding a permission check (LocatrFragment.java)

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_locate:
            if (hasLocationPermission()) {
                findImage();
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Now to write what should happen when you do not have location permission. In that case, you want to call the requestPermissions(…) method.

Listing 33.20  Requesting permission (LocatrFragment.java)

public class LocatrFragment extends Fragment {
    private static final String TAG = "LocatrFragment";
    private static final String[] LOCATION_PERMISSIONS = new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
    };
    private static final int REQUEST_LOCATION_PERMISSIONS = 0;
    ...
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_locate:
                if (hasLocationPermission()) {
                    findImage();
                } else {
                    requestPermissions(LOCATION_PERMISSIONS,
                            REQUEST_LOCATION_PERMISSIONS);
                }
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

The requestPermissions(…) method is an asynchronous request. When you call it, Android will display the system permissions dialog with a message appropriate to the permission you are requesting.

To respond to the system dialog, you write a corresponding implementation of onRequestPermissionsResult(…). Android will call this callback when the user presses ALLOW or DENY. Write an implementation that checks permission again and calls findImage() if the permission was granted.

Listing 33.21  Responding to a permissions request result (LocatrFragment.java)

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    ...
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                       int[] grantResults) {
    switch (requestCode) {
        case REQUEST_LOCATION_PERMISSIONS:
            if (hasLocationPermission()) {
                findImage();
            }
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

private void findImage() {
    LocationRequest request = LocationRequest.create();
    request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    request.setNumUpdates(1);

The callback to onRequestPermissionsResult(int,String[],int[]) includes a parameter called grantResults. If you like, you can check this parameter to see whether your permission was granted. Here, you take a slightly simpler approach: When you call hasLocationPermission(), its call to checkSelfPermission(…) will also tell you whether you have acquired the permission. So you can call hasLocationPermission() one more time and accomplish the same thing with less code than it takes to look into the contents of grantResults.

Run Locatr and press on the menu item again. This time, you will see the system permissions dialog pop up (Figure 33.9).

Figure 33.9  The LOCATION permission group dialog

Screenshot shows permissions system dialog box. A text reads, Allow Locatr to access this device's location. Deny and Allow buttons are placed below.

If you press ALLOW, then the permission will be granted until the user uninstalls the app or turns the permission off. If you press DENY, then it will be denied for the moment – the next time you press the button, the dialog will be shown again. (For debugging, we prefer to clear the permissions state by uninstalling. Uninstalling apps is much more fun to do on Android than turning permissions off, so that is how we do it.)

With that done, you can verify that the location behavior works as you expect. Remember to have MockWalker running if you are using an emulator. (If you have problems with the menu, flip back to Chapter 13 to integrate the AppCompat library.) Press the location button and accept the permission, and you should see a line something like this logged out:

...D/libEGL: loaded /system/lib/egl/libGLESv2_MRVL.so
...D/GC: <tid=12423> OES20 ===> GC Version   : GC Ver rls_pxa988_KK44_GC13.24
...D/OpenGLRenderer: Enabling debug mode 0
...I/LocatrFragment: Got a fix: Location[fused 33.758998,-84.331796 acc=38 et=...]

This shows you the latitude and longitude, accuracy, and the estimated time of the location fix. If you plug your lat-lon pair into Google Maps, you should be able to pull up your current location (Figure 33.10).

Figure 33.10  Our current location

Screenshot shows Google maps.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.200.95.157