- This sample shows runtime permissions available in the Android M and above. - Display the log to follow the execution. - If executed on an Android M device, an additional option to access contacts is shown. + This sample shows runtime permissions available in Android M and above. + Display the log on screen to follow the execution. + If executed on an Android M device, an additional option to access contacts is shown + that is declared with optional, M and above only permissions.
diff --git a/samples/browseable/RuntimePermissions/res/values/base-strings.xml b/samples/browseable/RuntimePermissions/res/values/base-strings.xml index 58d75f931..33c535ac6 100644 --- a/samples/browseable/RuntimePermissions/res/values/base-strings.xml +++ b/samples/browseable/RuntimePermissions/res/values/base-strings.xml @@ -21,9 +21,10 @@ diff --git a/samples/browseable/RuntimePermissions/res/values/strings.xml b/samples/browseable/RuntimePermissions/res/values/strings.xml index 82d7b719d..edd2c1532 100644 --- a/samples/browseable/RuntimePermissions/res/values/strings.xml +++ b/samples/browseable/RuntimePermissions/res/values/strings.xml @@ -1,5 +1,6 @@- * 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,62 +117,122 @@ 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."); - - // 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(); - } - - // Request Camera permission - requestPermissions(new String[]{Manifest.permission.CAMERA}, - REQUEST_CAMERA); } // 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. + // 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 { + + // Camera permission has not been granted yet. Request it directly. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, + REQUEST_CAMERA); + } + // END_INCLUDE(camera_permission_request) + } + /** * Called when the 'show camera' button is clicked. * Callback is defined in resource layout definition. */ 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(); - } + // For example, if the request has been denied previously. + Log.i(TAG, + "Displaying contacts permission rationale to provide additional context."); - // contact permissions has not been granted (read and write contacts). Request them. - requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS); + // 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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java index d0742ead9..b9be6258e 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java +++ b/samples/browseable/RuntimePermissions/src/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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java index b35bfebc0..d38195f57 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java +++ b/samples/browseable/RuntimePermissions/src/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/samples/browseable/RuntimePermissions/src/common.activities/SampleActivityBase.java b/samples/browseable/RuntimePermissions/src/common.activities/SampleActivityBase.java new file mode 100644 index 000000000..ac3928ef0 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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/samples/browseable/RuntimePermissionsBasic/_index.jd b/samples/browseable/RuntimePermissionsBasic/_index.jd index c4b5d4ca8..4fe611862 100644 --- a/samples/browseable/RuntimePermissionsBasic/_index.jd +++ b/samples/browseable/RuntimePermissionsBasic/_index.jd @@ -5,7 +5,6 @@ sample.group=System
- This sample shows runtime permissions available in the Android M and above.
This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
diff --git a/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml b/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml
index c3bf99c5f..146b8b1e7 100644
--- a/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml
+++ b/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml
@@ -14,27 +14,28 @@
limitations under the License.
-->
-
+ * Note that there is no need to check the API level, the support library
+ * already takes care of this. Similar helper methods for permissions are also available in
+ * ({@link ActivityCompat},
+ * {@link android.support.v4.content.ContextCompat} and {@link android.support.v4.app.Fragment}).
*/
-public class MainActivity extends Activity {
+public class MainActivity extends AppCompatActivity
+ implements ActivityCompat.OnRequestPermissionsResultCallback {
private static final int PERMISSION_REQUEST_CAMERA = 0;
+ private View mLayout;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ mLayout = findViewById(R.id.main_layout);
// Register a listener for the 'Show Camera Preview' button.
Button b = (Button) findViewById(R.id.button_open_camera);
@@ -69,15 +81,16 @@ public class MainActivity extends Activity {
// BEGIN_INCLUDE(onRequestPermissionsResult)
if (requestCode == PERMISSION_REQUEST_CAMERA) {
// Request for camera permission.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission has been granted. Start camera preview Activity.
- Toast.makeText(this, "Camera permission was granted. Starting preview.",
- Toast.LENGTH_SHORT)
+ Snackbar.make(mLayout, "Camera permission was granted. Starting preview.",
+ Snackbar.LENGTH_SHORT)
.show();
startCamera();
} else {
// Permission request was denied.
- Toast.makeText(this, "Camera permission request was denied.", Toast.LENGTH_SHORT)
+ Snackbar.make(mLayout, "Camera permission request was denied.",
+ Snackbar.LENGTH_SHORT)
.show();
}
}
@@ -86,51 +99,57 @@ public class MainActivity extends Activity {
private void showCameraPreview() {
// BEGIN_INCLUDE(startCamera)
- if (!isMNC()) {
- // Below Android M there is no need to check for runtime permissions
- Toast.makeText(this,
- "Requested permissions are granted at install time below M and are always "
- + "available at runtime.",
- Toast.LENGTH_SHORT).show();
- startCamera();
- return;
- }
-
// Check if the Camera permission has been granted
- if (checkSelfPermission(Manifest.permission.CAMERA)
- != PackageManager.PERMISSION_GRANTED) {
- // Permission has not been granted and must be requested.
-
- if (shouldShowRequestPermissionRationale(
- 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.
- Toast.makeText(this, "Camera access is required to display a camera preview.",
- Toast.LENGTH_SHORT).show();
- }
- Toast.makeText(this,
- "Permission is not available. Requesting camera permission.",
- Toast.LENGTH_SHORT).show();
-
- // Request the permission. The result will be received in onRequestPermissionResult()
- requestPermissions(new String[]{Manifest.permission.CAMERA},
- PERMISSION_REQUEST_CAMERA);
- } else {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
// Permission is already available, start camera preview
- startCamera();
- Toast.makeText(this,
+ Snackbar.make(mLayout,
"Camera permission is available. Starting preview.",
- Toast.LENGTH_SHORT).show();
+ Snackbar.LENGTH_SHORT).show();
+ startCamera();
+ } else {
+ // Permission is missing and must be requested.
+ requestCameraPermission();
}
// END_INCLUDE(startCamera)
}
+ /**
+ * Requests the {@link android.Manifest.permission#CAMERA} permission.
+ * If an additional rationale should be displayed, the user has to launch the request from
+ * a SnackBar that includes additional information.
+ */
+ private void requestCameraPermission() {
+ // Permission has not been granted and must be requested.
+ 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.
+ // Display a SnackBar with a button to request the missing permission.
+ Snackbar.make(mLayout, "Camera access is required to display the camera preview.",
+ Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Request the permission
+ ActivityCompat.requestPermissions(MainActivity.this,
+ new String[]{Manifest.permission.CAMERA},
+ PERMISSION_REQUEST_CAMERA);
+ }
+ }).show();
+
+ } else {
+ Snackbar.make(mLayout,
+ "Permission is not available. Requesting camera permission.",
+ Snackbar.LENGTH_SHORT).show();
+ // Request the permission. The result will be received in onRequestPermissionResult().
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
+ PERMISSION_REQUEST_CAMERA);
+ }
+ }
+
private void startCamera() {
Intent intent = new Intent(this, CameraPreviewActivity.class);
startActivity(intent);
}
- public static boolean isMNC() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
- }
}