The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Tutorial: Runtime Permission Support

Runtime permissions — as introduced in Android 6.0 — sound simple on the surface: just call checkSelfPermission() to see if you have the permission, then call requestPermissions() if you do not.

In practice, even a fairly simple app that uses these permissions has to add a remarkable amount of code, to handle all of the combinations of states, plus deal with some idiosyncrasies in the API. And, of course, since not everybody will be running a new device, we also have backwards compatibility to consider.

This standalone tutorial — not part of the EmPubLite series of tutorials throughout the rest of the core chapters — focuses on how to add the runtime permission support to an existing Android application.

As with the other code snippets in this book, if you are trying to copy and paste from the PDF itself, you will tend to have the best luck if you use the official Adobe Acrobat reader app.

If you prefer, you can work with the tutorial code from GitHub, including:

In particular, the latter link, being simple text, may be simpler to copy and paste from, for situations where we are modifying the code to directly match what will be in the completed project.

Also, as part of working on this tutorial, you will be adding many snippets of Java code. You will need to add import statements for the new classes introduced by those code snippets. Just click on the class name, highlighted in red, in Android Studio and press <Alt>-<Enter> to invoke the quick-fix to add the required import statement. Also, if you are trying to copy-and-paste from the PDF edition of this book, consider using the official Adobe Reader app, as it seems to have the least issues with copying the code properly.

This tutorial assumes that you have done at least Tutorial #1 from the EmPubLite series or otherwise are set up with Android Studio 2.3 and the Android 7.1 SDK.

Step #1: Import and Review the Starter Project

Download the starter project ZIP archive and unzip it somewhere on your development machine.

Then, use File > New > Import Project to import this project into Android Studio. Android Studio may prompt you for additional updates from the SDK Manager (e.g., build tools), depending upon what you have set up on your development machine.

If you run the project on an Android 4.0+ device or emulator, you will see our highly-sophisticated user interface, consisting of two big buttons:

Runtime Permissions Tutorial App, As Initially Written and Launched
Figure 279: Runtime Permissions Tutorial App, As Initially Written and Launched

Tapping the “Take Picture” button will bring up a camera preview, with a floating action button (FAB) to take a picture:

Runtime Permissions Tutorial App, Showing Camera Preview
Figure 280: Runtime Permissions Tutorial App, Showing Camera Preview

Tapping the FAB (and taking a picture) or pressing BACK will return you to the original two-button activity. There, tapping the “Record Video” button will bring up a similar activity, where you can press the green record FAB to start recording a video:

Runtime Permissions Tutorial App, Showing Video Preview
Figure 281: Runtime Permissions Tutorial App, Showing Video Preview

If you start recording, the FAB will change to a red stop button. Tapping that, or pressing BACK from either state, will return you to the initial two-button activity.

The application makes use of two third-party dependencies to pull all of this off:


dependencies {
    compile 'com.commonsware.cwac:cam2:0.7.4'
    compile 'com.githang:com-phillipcalvin-iconbutton:1.0.1@aar'
}

Our two layouts, res/layout/main.xml and res/layout-land/main.xml, have two IconButton widgets in a LinearLayout, with equal weights so the buttons each take up half of the screen:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="horizontal">

  <com.phillipcalvin.iconbutton.IconButton
    android:id="@+id/take_picture"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_margin="4dp"
    android:layout_weight="1"
    android:drawableRight="@drawable/ic_camera_black_48dp"
    android:onClick="takePicture"
    android:text="Take Picture"
    android:textAppearance="?android:attr/textAppearanceLarge"
    app:iconPadding="16dp"/>

  <com.phillipcalvin.iconbutton.IconButton
    android:id="@+id/record_video"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_margin="4dp"
    android:layout_weight="1"
    android:drawableRight="@drawable/ic_videocam_black_48dp"
    android:onClick="recordVideo"
    android:text="Record Video"
    android:textAppearance="?android:attr/textAppearanceLarge"
    app:iconPadding="16dp"/>
</LinearLayout>

MainActivity then uses CWAC-Cam2 to handle each of the button clicks:

package com.commonsware.android.perm.tutorial;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Toast;
import com.commonsware.cwac.cam2.CameraActivity;
import com.commonsware.cwac.cam2.VideoRecorderActivity;
import java.io.File;

public class MainActivity extends Activity {
  private static final int RESULT_PICTURE_TAKEN=1337;
  private static final int RESULT_VIDEO_RECORDED=1338;
  private File rootDir;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    File downloads=Environment
        .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

    rootDir=new File(downloads, "RuntimePermTutorial");
    rootDir.mkdirs();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode,
                                  Intent data) {
    Toast t=null;

    if (resultCode==RESULT_OK) {
      if (requestCode==RESULT_PICTURE_TAKEN) {
        t=Toast.makeText(this, R.string.msg_pic_taken,
            Toast.LENGTH_LONG);
      }
      else if (requestCode==RESULT_VIDEO_RECORDED) {
        t=Toast.makeText(this, R.string.msg_vid_recorded,
            Toast.LENGTH_LONG);
      }

      t.show();
    }
  }

  public void takePicture(View v) {
    takePictureForRealz();
  }

  public void recordVideo(View v) {
    recordVideoForRealz();
  }

  private void takePictureForRealz() {
    Intent i=new CameraActivity.IntentBuilder(MainActivity.this)
        .to(new File(rootDir, "test.jpg"))
        .updateMediaStore()
        .build();

    startActivityForResult(i, RESULT_PICTURE_TAKEN);
  }

  private void recordVideoForRealz() {
    Intent i=new VideoRecorderActivity.IntentBuilder(MainActivity.this)
        .quality(VideoRecorderActivity.Quality.HIGH)
        .sizeLimit(5000000)
        .to(new File(rootDir, "test.mp4"))
        .updateMediaStore()
        .forceClassic()
        .build();

    startActivityForResult(i, RESULT_VIDEO_RECORDED);
  }
}

The details of how CWAC-Cam2 works are not particularly relevant for the tutorial, but you can learn more about that later in the book if you are interested.

Taking pictures and recording videos require three permissions:

Our manifest asks for none of these permissions:

<?xml version="1.0" encoding="utf-8"?>
<manifest
  package="com.commonsware.android.perm.tutorial"
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:versionCode="1"
  android:versionName="1.0">

  <supports-screens
    android:anyDensity="true"
    android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:xlargeScreens="true"/>

  <application
    android:allowBackup="false"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/Theme.Apptheme">
    <activity
      android:name=".MainActivity"
      android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>

</manifest>

The permissions come from the CWAC-Cam2 library, courtesy of a process known as manifest merger.

You might wonder why we would bother doing this using a camera library in our own app. Most Android devices with camera hardware have a camera app, and most camera apps — particularly pre-installed camera apps — have activities that we could invoke to take pictures or record videos. However, these activities are infrequently tested, and many do not work properly. Since they are unreliable, you may be happier using something that is a library, packaged in your app.

Note that MainActivity has some seemingly superfluous bits of code. Specifically, we delegate the actual CWAC-Cam2 work to takePictureForRealz() and recordVideoForRealz(), instead of just doing that work in the takePicture() and recordVideo() methods invoked by the buttons. The reason for this apparent inefficiency is to reduce the amount of work it will take you to add the runtime permissions, by handling a tiny bit of bookkeeping ahead of time.

Step #2: Update Gradle for Android 6.0+

By default, if you run this app from your IDE on an Android 6.0 device, nothing appears to be different. The app runs as it did.

If you were to install it via a download, such as from a Web site, the installation process looks as it does on earlier Android versions, prompting the user for each of the permissions:

Installing the Tutorial App From the Web
Figure 282: Installing the Tutorial App From the Web

However, the user can still go into Settings and elect to disable our access to those permissions:

Settings, On Android 6.0, Showing Tutorial App Permissions
Figure 283: Settings, On Android 6.0, Showing Tutorial App Permissions

In our case, not all those permissions are always needed, and it would be useful to know whether or not we hold a permission, and so adopting the new runtime permission model would seem to be a good idea.

The first step on the road to doing that is to change the targetSdkVersion in our project from 15 to 25. This will give you an android closure like:

android {
  compileSdkVersion 25
  buildToolsVersion "25.0.2"
  
  defaultConfig {
    minSdkVersion 15
    targetSdkVersion 25
  }
}

Step #3: Review the Planned UX

The preview of this section is unavailable right now, but if you leave your name and number at the sound of the tone, it might get back to you (BEEEEEEEEEEEEP!).

Step #4: Detect the First Run

The preview of this section is en route to Mars.

Step #5: On First Run, Ask For Permissions

The preview of this section is being chased by zombies.

Step #6: Check for Permissions Before Taking a Picture

The preview of this section left for Hollywood to appear in a reality TV show.

Step #7: Detect If We Should Show Some Rationale

The preview of this section is out seeking fame and fortune as the Dread Pirate Roberts.

Step #8: Add a Rationale UI and Re-Request Permissions

The preview of this section took that left turn at Albuquerque.

Step #9: Check for Permissions Before Recording a Video

The preview of this section was fed to a gremlin, after midnight.

Step #10: Detect If We Should Show Some Rationale (Again)

The preview of this section was lost due to a rupture in the space-time continuum.

Step #11: Support Configuration Changes

The preview of this section was eaten by a grue.