From 02aad741ef92ddc9759575733202c15258b5a616 Mon Sep 17 00:00:00 2001 From: Jan-Felix Schmakeit Date: Tue, 4 Aug 2015 17:25:03 +1000 Subject: Update RuntimePermissions sample for API 23. Use compat calls from v4 support library for all permission checks and requests. Also removed M-only checks and lowered minSDK to 15. Use a SnackBar instead of a Toast to display messages. Change-Id: I53f8e5354e4d99060eeac41241a44bd931afd7a8 --- .../system/runtimepermissions/MainActivity.java | 171 +++++++++++++++------ .../system/runtimepermissions/PermissionUtil.java | 45 +----- .../RuntimePermissionsFragment.java | 11 +- .../java/common/activities/SampleActivityBase.java | 53 +++++++ .../Application/src/main/res/values/strings.xml | 3 +- system/RuntimePermissions/template-params.xml | 35 +++-- 6 files changed, 210 insertions(+), 108 deletions(-) create mode 100644 system/RuntimePermissions/Application/src/main/java/common/activities/SampleActivityBase.java (limited to 'system') diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java index 5f38bad8..7abc538c 100644 --- a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java +++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java @@ -16,7 +16,6 @@ package com.example.android.system.runtimepermissions; -import com.example.android.common.activities.SampleActivityBase; import com.example.android.common.logger.Log; import com.example.android.common.logger.LogFragment; import com.example.android.common.logger.LogWrapper; @@ -26,15 +25,20 @@ import com.example.android.system.runtimepermissions.contacts.ContactsFragment; import android.Manifest; import android.app.Activity; +import android.content.Context; import android.content.pm.PackageManager; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentTransaction; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.Toast; import android.widget.ViewAnimator; +import common.activities.SampleActivityBase; + /** * Launcher Activity that demonstrates the use of runtime permissions for Android M. * It contains a summary sample description, sample log and a Fragment that calls callbacks on this @@ -46,15 +50,18 @@ import android.widget.ViewAnimator; * android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts' * button is * clicked to display the first contact in the contacts database and to add a dummy contact - * directly - * to it. First, permissions are checked if they have already been granted through {@link - * android.app.Activity#checkSelfPermission(String)} (wrapped in {@link - * PermissionUtil#hasSelfPermission(Activity, String)} and {@link PermissionUtil#hasSelfPermission(Activity, - * String[])} for compatibility). If permissions have not been granted, they are requested through - * {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link - * Activity#onRequestPermissionsResult(int, String[], int[])}. + * directly to it. Permissions are verified and requested through compat helpers in the support v4 + * library, in this Activity using {@link ActivityCompat}. + * First, permissions are checked if they have already been granted through {@link + * ActivityCompat#checkSelfPermission(Context, String)}. + * If permissions have not been granted, they are requested through + * {@link ActivityCompat#requestPermissions(Activity, String[], int)} and the return value checked + * in + * a callback to the {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback} + * interface. *

- * Before requesting permissions, {@link Activity#shouldShowRequestPermissionRationale(String)} + * Before requesting permissions, {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity, + * String)} * should be called to provide the user with additional context for the use of permissions if they * have been denied previously. *

@@ -73,7 +80,8 @@ import android.widget.ViewAnimator; *

* (This class is based on the MainActivity used in the SimpleFragment sample template.) */ -public class MainActivity extends SampleActivityBase { +public class MainActivity extends SampleActivityBase + implements ActivityCompat.OnRequestPermissionsResultCallback { public static final String TAG = "MainActivity"; @@ -96,6 +104,10 @@ public class MainActivity extends SampleActivityBase { // Whether the Log Fragment is currently shown. private boolean mLogShown; + /** + * Root of the layout of this Activity. + */ + private View mLayout; /** * Called when the 'show camera' button is clicked. @@ -105,30 +117,57 @@ public class MainActivity extends SampleActivityBase { Log.i(TAG, "Show camera button pressed. Checking permission."); // BEGIN_INCLUDE(camera_permission) // Check if the Camera permission is already available. - if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + // Camera permission has not been granted. + + requestCameraPermission(); + + } else { + // Camera permissions is already available, show the camera preview. Log.i(TAG, "CAMERA permission has already been granted. Displaying camera preview."); showCameraPreview(); - } else { - // Camera permission has not been granted. - Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission."); + } + // END_INCLUDE(camera_permission) + } + + /** + * Requests the Camera permission. + * If the permission has been denied previously, a SnackBar will prompt the user to grant the + * permission, otherwise it is requested directly. + */ + private void requestCameraPermission() { + Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission."); + + // BEGIN_INCLUDE(camera_permission_request) + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.CAMERA)) { // Provide an additional rationale to the user if the permission was not granted // and the user would benefit from additional context for the use of the permission. - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - Log.i(TAG, - "Displaying camera permission rationale to provide additional context."); - Toast.makeText(this, R.string.permission_camera_rationale, Toast.LENGTH_SHORT) - .show(); - } + // For example if the user has previously denied the permission. + Log.i(TAG, + "Displaying camera permission rationale to provide additional context."); + Snackbar.make(mLayout, R.string.permission_camera_rationale, + Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.ok, new View.OnClickListener() { + @Override + public void onClick(View view) { + ActivityCompat.requestPermissions(MainActivity.this, + new String[]{Manifest.permission.CAMERA}, + REQUEST_CAMERA); + } + }) + .show(); + } else { - // Request Camera permission - requestPermissions(new String[]{Manifest.permission.CAMERA}, + // Camera permission has not been granted yet. Request it directly. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } - // END_INCLUDE(camera_permission) - + // END_INCLUDE(camera_permission_request) } /** @@ -137,30 +176,63 @@ public class MainActivity extends SampleActivityBase { */ public void showContacts(View v) { Log.i(TAG, "Show contacts button pressed. Checking permissions."); + // Verify that all required contact permissions have been granted. - if (PermissionUtil.hasSelfPermission(this, PERMISSIONS_CONTACT)) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) + != PackageManager.PERMISSION_GRANTED + || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) + != PackageManager.PERMISSION_GRANTED) { + // Contacts permissions have not been granted. + Log.i(TAG, "Contact permissions has NOT been granted. Requesting permissions."); + requestContactsPermissions(); + + } else { + + // Contact permissions have been granted. Show the contacts fragment. Log.i(TAG, "Contact permissions have already been granted. Displaying contact details."); - // Contact permissions have been granted. Show the contacts fragment. showContactDetails(); - } else { - // Contacts permissions have not been granted. - Log.i(TAG, "Contact permissions has NOT been granted. Requesting permission."); + } + } + + /** + * Requests the Contacts permissions. + * If the permission has been denied previously, a SnackBar will prompt the user to grant the + * permission, otherwise it is requested directly. + */ + private void requestContactsPermissions() { + // BEGIN_INCLUDE(contacts_permission_request) + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.READ_CONTACTS) + || ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.WRITE_CONTACTS)) { // Provide an additional rationale to the user if the permission was not granted // and the user would benefit from additional context for the use of the permission. - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - Log.i(TAG, - "Displaying contacts permission rationale to provide additional context."); - Toast.makeText(this, R.string.permission_contacts_rationale, Toast.LENGTH_SHORT) - .show(); - } - - // contact permissions has not been granted (read and write contacts). Request them. - requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS); + // For example, if the request has been denied previously. + Log.i(TAG, + "Displaying contacts permission rationale to provide additional context."); + + // Display a SnackBar with an explanation and a button to trigger the request. + Snackbar.make(mLayout, R.string.permission_contacts_rationale, + Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.ok, new View.OnClickListener() { + @Override + public void onClick(View view) { + ActivityCompat + .requestPermissions(MainActivity.this, PERMISSIONS_CONTACT, + REQUEST_CONTACTS); + } + }) + .show(); + } else { + // Contact permissions have not been granted yet. Request them directly. + ActivityCompat.requestPermissions(this, PERMISSIONS_CONTACT, REQUEST_CONTACTS); } + // END_INCLUDE(contacts_permission_request) } + /** * Display the {@link CameraPreviewFragment} in the content area if the required Camera * permission has been granted. @@ -189,8 +261,8 @@ public class MainActivity extends SampleActivityBase { * Callback received when a permissions request has been completed. */ @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { if (requestCode == REQUEST_CAMERA) { // BEGIN_INCLUDE(permission_result) @@ -198,14 +270,15 @@ public class MainActivity extends SampleActivityBase { Log.i(TAG, "Received response for Camera permission request."); // Check if the only required permission has been granted - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Camera permission has been granted, preview can be displayed Log.i(TAG, "CAMERA permission has now been granted. Showing preview."); - Toast.makeText(this, R.string.permision_available_camera, Toast.LENGTH_SHORT) - .show(); + Snackbar.make(mLayout, R.string.permision_available_camera, + Snackbar.LENGTH_SHORT).show(); } else { Log.i(TAG, "CAMERA permission was NOT granted."); - Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show(); + Snackbar.make(mLayout, R.string.permissions_not_granted, + Snackbar.LENGTH_SHORT).show(); } // END_INCLUDE(permission_result) @@ -217,11 +290,14 @@ public class MainActivity extends SampleActivityBase { // checked. if (PermissionUtil.verifyPermissions(grantResults)) { // All required permissions have been granted, display contacts fragment. - Toast.makeText(this, R.string.permision_available_contacts, Toast.LENGTH_SHORT) + Snackbar.make(mLayout, R.string.permision_available_contacts, + Snackbar.LENGTH_SHORT) .show(); } else { Log.i(TAG, "Contacts permissions were NOT granted."); - Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show(); + Snackbar.make(mLayout, R.string.permissions_not_granted, + Snackbar.LENGTH_SHORT) + .show(); } } else { @@ -291,6 +367,7 @@ public class MainActivity extends SampleActivityBase { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + mLayout = findViewById(R.id.sample_main_layout); if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java index d0742ead..b9be6258 100644 --- a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java +++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java @@ -18,7 +18,6 @@ package com.example.android.system.runtimepermissions; import android.app.Activity; import android.content.pm.PackageManager; -import android.os.Build; /** * Utility class that wraps access to the runtime permissions API in M and provides basic helper @@ -33,6 +32,11 @@ public abstract class PermissionUtil { * @see Activity#onRequestPermissionsResult(int, String[], int[]) */ public static boolean verifyPermissions(int[] grantResults) { + // At least one result must be checked. + if(grantResults.length < 1){ + return false; + } + // Verify that each required permission has been granted, otherwise return false. for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { @@ -42,43 +46,4 @@ public abstract class PermissionUtil { return true; } - /** - * Returns true if the Activity has access to all given permissions. - * Always returns true on platforms below M. - * - * @see Activity#checkSelfPermission(String) - */ - public static boolean hasSelfPermission(Activity activity, String[] permissions) { - // Below Android M all permissions are granted at install time and are already available. - if (!isMNC()) { - return true; - } - - // Verify that all required permissions have been granted - for (String permission : permissions) { - if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - return true; - } - - /** - * Returns true if the Activity has access to a given permission. - * Always returns true on platforms below M. - * - * @see Activity#checkSelfPermission(String) - */ - public static boolean hasSelfPermission(Activity activity, String permission) { - // Below Android M all permissions are granted at install time and are already available. - if (!isMNC()) { - return true; - } - - return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; - } - - public static boolean isMNC() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - } } diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java index b35bfebc..d38195f5 100644 --- a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java +++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java @@ -16,6 +16,7 @@ package com.example.android.system.runtimepermissions; +import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -32,16 +33,16 @@ public class RuntimePermissionsFragment extends Fragment { View root = inflater.inflate(R.layout.fragment_main, null); // BEGIN_INCLUDE(m_only_permission) - if (!PermissionUtil.isMNC()) { + if (Build.VERSION.SDK_INT < 23) { /* - The contacts permissions have been declared in the AndroidManifest for Android M only. - They are not available on older platforms, so we are hiding the button to access the - contacts database. + The contacts permissions have been declared in the AndroidManifest for Android M and + above only. They are not available on older platforms, so we are hiding the button to + access the contacts database. This shows how new runtime-only permissions can be added, that do not apply to older platform versions. This can be useful for automated updates where additional permissions might prompt the user on upgrade. */ - root.findViewById(R.id.button_camera).setVisibility(View.GONE); + root.findViewById(R.id.button_contacts).setVisibility(View.GONE); } // END_INCLUDE(m_only_permission) diff --git a/system/RuntimePermissions/Application/src/main/java/common/activities/SampleActivityBase.java b/system/RuntimePermissions/Application/src/main/java/common/activities/SampleActivityBase.java new file mode 100644 index 00000000..ac3928ef --- /dev/null +++ b/system/RuntimePermissions/Application/src/main/java/common/activities/SampleActivityBase.java @@ -0,0 +1,53 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package common.activities; + +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogWrapper; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + + +/** + * Base launcher activity, to handle most of the common plumbing for samples. + */ +public class SampleActivityBase extends AppCompatActivity { + + public static final String TAG = "SampleActivityBase"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + super.onStart(); + initializeLogging(); + } + + /** Set up targets to receive log data */ + public void initializeLogging() { + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + // Wraps Android's native log framework + LogWrapper logWrapper = new LogWrapper(); + Log.setLogNode(logWrapper); + + Log.i(TAG, "Ready"); + } +} diff --git a/system/RuntimePermissions/Application/src/main/res/values/strings.xml b/system/RuntimePermissions/Application/src/main/res/values/strings.xml index 82d7b719..edd2c153 100644 --- a/system/RuntimePermissions/Application/src/main/res/values/strings.xml +++ b/system/RuntimePermissions/Application/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ + OK Total number of contacts: %1$,d\nFirst contact:%2$s No contacts stored on device. Contacts not loaded. @@ -17,5 +18,5 @@ Contacts Permissions have been granted. Contacts screen can now be opened. Permissions were not granted. Camera permission is needed to show the camera preview. - Contacts permissions are needed to demonstrate access to the contacts database. + Contacts permissions are needed to demonstrate access. diff --git a/system/RuntimePermissions/template-params.xml b/system/RuntimePermissions/template-params.xml index 445b6a39..52e9f9fd 100644 --- a/system/RuntimePermissions/template-params.xml +++ b/system/RuntimePermissions/template-params.xml @@ -19,23 +19,25 @@ System com.example.android.system.runtimepermissions - 23 + 15 - com.android.support:appcompat-v7:21.+ + com.android.support:appcompat-v7:23.0.0 + com.android.support:support-v4:23.0.0 + com.android.support:design:23.0.0