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

Android 6.0’s runtime permissions 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.

Step #0: Install the Android 6.0 SDK

You are going to need the Android 6.0 (API 23) SDK Platform (or higher) in order to be able to implement runtime permission support. You may already have it, or you may need to install it.

If you open up Android Studio’s SDK Manager, via Tools > Android > “SDK Manager”, you should see Android 6.0 as an option:

Android Studio SDK Manager
Figure 278: Android Studio SDK Manager

If you have a device with Android 6.0+ on it, you are welcome to run the sample app, and it should allow you to take pictures and record videos. If you wish to run the sample app on an Android 6.0+ emulator, the permissions logic that we will be adding to the tutorial app will work, but it will not actually take pictures or record video. If your emulator image has 1+ cameras configured (see the “Advanced Settings” button when defining or editing your AVD), the activities to take a picture and record a video will come up but just show an indefinite progress indicator. If your emulator image has no cameras configured, those activities will just immediately finish and return control to our sample app’s main activity.

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.2'
    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+

The preview of this section is being chased by zombies.

Step #3: Review the Planned UX

The preview of this section is in an invisible, microscopic font.

Step #4: Detect the First Run

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

Step #5: On First Run, Ask For Permissions

The preview of this section was accidentally identified as an Android 'tasty treat' by the Cookie Monster.

Step #6: Check for Permissions Before Taking a Picture

The preview of this section was lost in the sofa cushions.

Step #7: Detect If We Should Show Some Rationale

The preview of this section was stepped on by Godzilla.

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

The preview of this section was traded for a bag of magic beans.

Step #9: Check for Permissions Before Recording a Video

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

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

The preview of this section was lost in the sofa cushions.

Step #11: Support Configuration Changes

The preview of this section is in the process of being translated from its native Klingon.