Merge "Updating sample to use Android M permissions model." into mnc-dev
diff --git a/media/HdrViewfinder/Application/src/main/AndroidManifest.xml b/media/HdrViewfinder/Application/src/main/AndroidManifest.xml
index 772b7df..63066f6 100644
--- a/media/HdrViewfinder/Application/src/main/AndroidManifest.xml
+++ b/media/HdrViewfinder/Application/src/main/AndroidManifest.xml
@@ -20,10 +20,6 @@
     android:versionCode="1"
     android:versionName="1.0">
 
-    <uses-sdk
-        android:minSdkVersion="21"
-        android:targetSdkVersion="21"/>
-
     <uses-feature android:name="android.hardware.camera"/>
     <uses-feature
         android:name="android.hardware.camera.front"
@@ -31,8 +27,6 @@
 
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 
     <application
         android:allowBackup="true"
@@ -43,7 +37,8 @@
         <activity
             android:name=".HdrViewfinderActivity"
             android:label="@string/app_name"
-            android:screenOrientation="landscape">
+            android:screenOrientation="landscape"
+                android:theme="@style/Theme.AppCompat.Light">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/CameraOps.java b/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/CameraOps.java
index 5769760..173ef08 100644
--- a/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/CameraOps.java
+++ b/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/CameraOps.java
@@ -78,7 +78,7 @@
     }
 
     /**
-     * Open the first backfacing camera listed by the camera manager.
+     * Open the first back-facing camera listed by the camera manager.
      * Displays a dialog if it cannot open a camera.
      */
     public void openCamera(final String cameraId) {
diff --git a/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/HdrViewfinderActivity.java b/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/HdrViewfinderActivity.java
index 79f1bb6..ca49ea0 100644
--- a/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/HdrViewfinderActivity.java
+++ b/media/HdrViewfinder/Application/src/main/java/com/example/android/hdrviewfinder/HdrViewfinderActivity.java
@@ -16,7 +16,9 @@
 
 package com.example.android.hdrviewfinder;
 
-import android.app.Activity;
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -26,10 +28,16 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 import android.renderscript.RenderScript;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
 import android.util.Size;
 import android.view.GestureDetector;
@@ -76,19 +84,26 @@
  * Android {@link android.view.Surface} class, which allows for zero-copy transport of large
  * buffers between processes and subsystems.</p>
  */
-public class HdrViewfinderActivity extends Activity implements
+public class HdrViewfinderActivity extends AppCompatActivity implements
         SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener {
 
     private static final String TAG = "HdrViewfinderDemo";
 
     private static final String FRAGMENT_DIALOG = "dialog";
 
+    private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
+
     /**
      * View for the camera preview.
      */
     private FixedAspectSurfaceView mPreviewView;
 
     /**
+     * Root view of this activity.
+     */
+    private View rootView;
+
+    /**
      * This shows the current mode of the app.
      */
     private TextView mModeText;
@@ -132,6 +147,8 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
 
+        rootView = findViewById(R.id.panels);
+
         mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview);
         mPreviewView.getHolder().addCallback(this);
         mPreviewView.setGestureListener(this, mViewListener);
@@ -146,23 +163,20 @@
 
         mUiHandler = new Handler(Looper.getMainLooper());
 
-        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
-        mCameraOps = new CameraOps(mCameraManager,
-                /*errorDisplayer*/ this,
-                /*readyListener*/ this,
-                /*readyHandler*/ mUiHandler);
-
-        mHdrRequests.add(null);
-        mHdrRequests.add(null);
-
         mRS = RenderScript.create(this);
+
+        // When permissions are revoked the app is restarted so onCreate is sufficient to check for
+        // permissions core to the Activity's functionality.
+        if (!checkCameraPermissions()) {
+            requestCameraPermissions();
+        } else {
+            findAndOpenCamera();
+        }
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-
-        findAndOpenCamera();
     }
 
     @Override
@@ -170,7 +184,10 @@
         super.onPause();
 
         // Wait until camera is closed to ensure the next application can open it
-        mCameraOps.closeCameraAndWait();
+        if (mCameraOps != null) {
+            mCameraOps.closeCameraAndWait();
+            mCameraOps = null;
+        }
     }
 
     @Override
@@ -232,7 +249,9 @@
         }
     };
 
-    // Show help dialog
+    /**
+     * Show help dialogs.
+     */
     private View.OnClickListener mHelpButtonListener = new View.OnClickListener() {
         public void onClick(View v) {
             MessageDialogFragment.newInstance(R.string.help_text)
@@ -240,54 +259,176 @@
         }
     };
 
+    /**
+     * Return the current state of the camera permissions.
+     */
+    private boolean checkCameraPermissions() {
+        int permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
+
+        // Check if the Camera permission is already available.
+        if (permissionState != PackageManager.PERMISSION_GRANTED) {
+            // Camera permission has not been granted.
+            Log.i(TAG, "CAMERA permission has NOT been granted.");
+            return false;
+        } else {
+            // Camera permissions are available.
+            Log.i(TAG, "CAMERA permission has already been granted.");
+            return true;
+        }
+    }
+
+    /**
+     * Attempt to initialize the camera.
+     */
+    private void initializeCamera() {
+        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
+        if (mCameraManager != null) {
+            mCameraOps = new CameraOps(mCameraManager,
+                /*errorDisplayer*/ this,
+                /*readyListener*/ this,
+                /*readyHandler*/ mUiHandler);
+
+            mHdrRequests.add(null);
+            mHdrRequests.add(null);
+        } else {
+            Log.e(TAG, "Couldn't initialize the camera");
+        }
+    }
+
+    private void requestCameraPermissions() {
+        boolean shouldProvideRationale =
+            ActivityCompat.shouldShowRequestPermissionRationale(this,
+                    Manifest.permission.CAMERA);
+
+        // Provide an additional rationale to the user. This would happen if the user denied the
+        // request previously, but didn't check the "Don't ask again" checkbox.
+        if (shouldProvideRationale) {
+            Log.i(TAG, "Displaying camera permission rationale to provide additional context.");
+            Snackbar.make(rootView, R.string.camera_permission_rationale, Snackbar
+                    .LENGTH_INDEFINITE)
+                    .setAction(R.string.ok, new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            // Request Camera permission
+                            ActivityCompat.requestPermissions(HdrViewfinderActivity.this,
+                                    new String[]{Manifest.permission.CAMERA},
+                                    REQUEST_PERMISSIONS_REQUEST_CODE);
+                        }
+                    })
+                    .show();
+        } else {
+            Log.i(TAG, "Requesting camera permission");
+            // Request Camera permission. It's possible this can be auto answered if device policy
+            // sets the permission in a given state or the user denied the permission
+            // previously and checked "Never ask again".
+            ActivityCompat.requestPermissions(HdrViewfinderActivity.this,
+                    new String[]{Manifest.permission.CAMERA},
+                    REQUEST_PERMISSIONS_REQUEST_CODE);
+        }
+    }
+
+    /**
+     * Callback received when a permissions request has been completed.
+     */
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        Log.i(TAG, "onRequestPermissionResult");
+        if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
+            if (grantResults.length <= 0) {
+                // If user interaction was interrupted, the permission request is cancelled and you
+                // receive empty arrays.
+                Log.i(TAG, "User interaction was cancelled.");
+            } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                // Permission was granted.
+                findAndOpenCamera();
+            } else {
+                // Permission denied.
+
+                // In this Activity we've chosen to notify the user that they
+                // have rejected a core permission for the app since it makes the Activity useless.
+                // We're communicating this message in a Snackbar since this is a sample app, but
+                // core permissions would typically be best requested during a welcome-screen flow.
+
+                // Additionally, it is important to remember that a permission might have been
+                // rejected without asking the user for permission (device policy or "Never ask
+                // again" prompts). Therefore, a user interface affordance is typically implemented
+                // when permissions are denied. Otherwise, your app could appear unresponsive to
+                // touches or interactions which have required permissions.
+                Snackbar.make(rootView, R.string.camera_permission_denied_explanation, Snackbar
+                        .LENGTH_INDEFINITE)
+                        .setAction(R.string.settings, new View.OnClickListener() {
+                            @Override
+                            public void onClick(View view) {
+                                // Build intent that displays the App settings screen.
+                                Intent intent = new Intent();
+                                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                                Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null);
+                                intent.setData(uri);
+                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                                startActivity(intent);
+                            }
+                        })
+                        .show();
+            }
+        }
+    }
+
     private void findAndOpenCamera() {
+        boolean cameraPermissions = checkCameraPermissions();
+        if (cameraPermissions) {
+            String errorMessage = "Unknown error";
+            boolean foundCamera = false;
+            initializeCamera();
+            if (cameraPermissions && mCameraOps != null) {
+                try {
+                    // Find first back-facing camera that has necessary capability.
+                    String[] cameraIds = mCameraManager.getCameraIdList();
+                    for (String id : cameraIds) {
+                        CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
+                        int facing = info.get(CameraCharacteristics.LENS_FACING);
 
-        String errorMessage = "Unknown error";
-        boolean foundCamera = false;
-        try {
-            // Find first back-facing camera that has necessary capability
-            String[] cameraIds = mCameraManager.getCameraIdList();
-            for (String id : cameraIds) {
-                CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
-                int facing = info.get(CameraCharacteristics.LENS_FACING);
+                        int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+                        boolean hasFullLevel
+                                = (level
+                                == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
 
-                int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
-                boolean hasFullLevel
-                        = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
+                        int[] capabilities = info
+                                .get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+                        int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
+                        boolean hasManualControl = hasCapability(capabilities,
+                                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
+                        boolean hasEnoughCapability = hasManualControl &&
+                                syncLatency
+                                        == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
 
-                int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
-                int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
-                boolean hasManualControl = hasCapability(capabilities,
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
-                boolean hasEnoughCapability = hasManualControl &&
-                        syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
-
-                // All these are guaranteed by
-                // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking for only
-                // the things we care about expands range of devices we can run on
-                // We want:
-                //  - Back-facing camera
-                //  - Manual sensor control
-                //  - Per-frame synchronization (so that exposure can be changed every frame)
-                if (facing == CameraCharacteristics.LENS_FACING_BACK &&
-                        (hasFullLevel || hasEnoughCapability)) {
-                    // Found suitable camera - get info, open, and set up outputs
-                    mCameraInfo = info;
-                    mCameraOps.openCamera(id);
-                    configureSurfaces();
-                    foundCamera = true;
-                    break;
+                        // All these are guaranteed by
+                        // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking
+                        // for only the things we care about expands range of devices we can run on.
+                        // We want:
+                        //  - Back-facing camera
+                        //  - Manual sensor control
+                        //  - Per-frame synchronization (so that exposure can be changed every frame)
+                        if (facing == CameraCharacteristics.LENS_FACING_BACK &&
+                                (hasFullLevel || hasEnoughCapability)) {
+                            // Found suitable camera - get info, open, and set up outputs
+                            mCameraInfo = info;
+                            mCameraOps.openCamera(id);
+                            configureSurfaces();
+                            foundCamera = true;
+                            break;
+                        }
+                    }
+                    if (!foundCamera) {
+                        errorMessage = getString(R.string.camera_no_good);
+                    }
+                } catch (CameraAccessException e) {
+                    errorMessage = getErrorString(e);
+                }
+                if (!foundCamera) {
+                    showErrorDialog(errorMessage);
                 }
             }
-            if (!foundCamera) {
-                errorMessage = getString(R.string.camera_no_good);
-            }
-        } catch (CameraAccessException e) {
-            errorMessage = getErrorString(e);
-        }
-
-        if (!foundCamera) {
-            showErrorDialog(errorMessage);
         }
     }
 
@@ -299,23 +440,25 @@
     }
 
     private void switchRenderMode(int direction) {
-        mRenderMode = (mRenderMode + direction) % 3;
+        if (mCameraOps != null) {
+            mRenderMode = (mRenderMode + direction) % 3;
 
-        mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]);
+            mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]);
 
-        if (mProcessor != null) {
-            mProcessor.setRenderMode(mRenderMode);
-        }
-        if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
-            mCameraOps.setRepeatingRequest(mPreviewRequest,
-                    mCaptureCallback, mUiHandler);
-        } else {
-            setHdrBurst();
+            if (mProcessor != null) {
+                mProcessor.setRenderMode(mRenderMode);
+            }
+            if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
+                mCameraOps.setRepeatingRequest(mPreviewRequest,
+                        mCaptureCallback, mUiHandler);
+            } else {
+                setHdrBurst();
+            }
         }
     }
 
     /**
-     * Configure the surfaceview and RS processing
+     * Configure the surfaceview and RS processing.
      */
     private void configureSurfaces() {
         // Find a good size for output - largest 16:9 aspect ratio that's less than 720p
diff --git a/media/HdrViewfinder/Application/src/main/res/layout/main.xml b/media/HdrViewfinder/Application/src/main/res/layout/main.xml
index 7507709..6fe56ef 100644
--- a/media/HdrViewfinder/Application/src/main/res/layout/main.xml
+++ b/media/HdrViewfinder/Application/src/main/res/layout/main.xml
@@ -15,13 +15,14 @@
      limitations under the License.
 -->
 <LinearLayout
-    android:id="@+id/panels"
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:custom="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/panels"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    tools:context="com.example.android.hdrviewfinder.HdrViewfinderActivity">
 
     <com.example.android.hdrviewfinder.FixedAspectSurfaceView
         android:id="@+id/preview"
@@ -36,7 +37,8 @@
         android:layout_width="0px"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:orientation="vertical">
+        android:orientation="vertical"
+        android:layout_margin="5dp">
 
         <Button
             android:id="@+id/help_button"
diff --git a/media/HdrViewfinder/Application/src/main/res/values/strings.xml b/media/HdrViewfinder/Application/src/main/res/values/strings.xml
index 21838d5..d910ab1 100644
--- a/media/HdrViewfinder/Application/src/main/res/values/strings.xml
+++ b/media/HdrViewfinder/Application/src/main/res/values/strings.xml
@@ -50,10 +50,18 @@
 
     <string name="info">Info</string>
 
+    <string name="camera_permission_rationale">This sample app requires camera access in order to
+        demo the API.</string>
     <string name="camera_no_good">No back-facing sufficiently capable camera available!</string>
     <string name="camera_disabled">Camera is disabled by device policy</string>
     <string name="camera_disconnected">Camera was disconnected before it was opened</string>
     <string name="camera_error">Camera service reported an error</string>
     <string name="camera_unknown">Unknown camera error: %s</string>
+    <string name="camera_permission_denied_explanation">You\'ve denied a permission that the app
+        needs for core functionality. If you selected &quot;don\'t ask again&quot; in the past then
+        you need to use Settings to re-enable the permission.</string>
+
+    <string name="ok">OK</string>
+    <string name="settings">Settings</string>
 
 </resources>
diff --git a/media/HdrViewfinder/template-params.xml b/media/HdrViewfinder/template-params.xml
index 915e09f..183aad8 100644
--- a/media/HdrViewfinder/template-params.xml
+++ b/media/HdrViewfinder/template-params.xml
@@ -20,6 +20,8 @@
     <package>com.example.android.hdrviewfinder</package>
 
     <minSdk>21</minSdk>
+    <targetSdkVersion>23</targetSdkVersion>
+    <compileSdkVersion>23</compileSdkVersion>
 
     <strings>
         <intro>