1 /* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.system.runtimepermissions; 18 19 import com.example.android.common.logger.Log; 20 import com.example.android.common.logger.LogFragment; 21 import com.example.android.common.logger.LogWrapper; 22 import com.example.android.common.logger.MessageOnlyLogFilter; 23 import com.example.android.system.runtimepermissions.camera.CameraPreviewFragment; 24 import com.example.android.system.runtimepermissions.contacts.ContactsFragment; 25 26 import android.Manifest; 27 import android.app.Activity; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.os.Bundle; 31 import android.support.annotation.NonNull; 32 import android.support.design.widget.Snackbar; 33 import android.support.v4.app.ActivityCompat; 34 import android.support.v4.app.FragmentTransaction; 35 import android.view.Menu; 36 import android.view.MenuItem; 37 import android.view.View; 38 import android.widget.ViewAnimator; 39 40 import common.activities.SampleActivityBase; 41 42 /** 43 * Launcher Activity that demonstrates the use of runtime permissions for Android M. 44 * It contains a summary sample description, sample log and a Fragment that calls callbacks on this 45 * Activity to illustrate parts of the runtime permissions API. 46 * <p> 47 * This Activity requests permissions to access the camera ({@link android.Manifest.permission#CAMERA}) 48 * when the 'Show Camera' button is clicked to display the camera preview. 49 * Contacts permissions (({@link android.Manifest.permission#READ_CONTACTS} and ({@link 50 * android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts' 51 * button is 52 * clicked to display the first contact in the contacts database and to add a placeholder contact 53 * directly to it. Permissions are verified and requested through compat helpers in the support v4 54 * library, in this Activity using {@link ActivityCompat}. 55 * First, permissions are checked if they have already been granted through {@link 56 * ActivityCompat#checkSelfPermission(Context, String)}. 57 * If permissions have not been granted, they are requested through 58 * {@link ActivityCompat#requestPermissions(Activity, String[], int)} and the return value checked 59 * in 60 * a callback to the {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback} 61 * interface. 62 * <p> 63 * Before requesting permissions, {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity, 64 * String)} 65 * should be called to provide the user with additional context for the use of permissions if they 66 * have been denied previously. 67 * <p> 68 * If this sample is executed on a device running a platform version below M, all permissions 69 * declared 70 * in the Android manifest file are always granted at install time and cannot be requested at run 71 * time. 72 * <p> 73 * This sample targets the M platform and must therefore request permissions at runtime. Change the 74 * targetSdk in the file 'Application/build.gradle' to 22 to run the application in compatibility 75 * mode. 76 * Now, if a permission has been disable by the system through the application settings, disabled 77 * APIs provide compatibility data. 78 * For example the camera cannot be opened or an empty list of contacts is returned. No special 79 * action is required in this case. 80 * <p> 81 * (This class is based on the MainActivity used in the SimpleFragment sample template.) 82 */ 83 public class MainActivity extends SampleActivityBase 84 implements ActivityCompat.OnRequestPermissionsResultCallback { 85 86 public static final String TAG = "MainActivity"; 87 88 /** 89 * Id to identify a camera permission request. 90 */ 91 private static final int REQUEST_CAMERA = 0; 92 93 /** 94 * Id to identify a contacts permission request. 95 */ 96 private static final int REQUEST_CONTACTS = 1; 97 98 /** 99 * Permissions required to read and write contacts. Used by the {@link ContactsFragment}. 100 */ 101 private static String[] PERMISSIONS_CONTACT = {Manifest.permission.READ_CONTACTS, 102 Manifest.permission.WRITE_CONTACTS}; 103 104 // Whether the Log Fragment is currently shown. 105 private boolean mLogShown; 106 107 /** 108 * Root of the layout of this Activity. 109 */ 110 private View mLayout; 111 112 /** 113 * Called when the 'show camera' button is clicked. 114 * Callback is defined in resource layout definition. 115 */ showCamera(View view)116 public void showCamera(View view) { 117 Log.i(TAG, "Show camera button pressed. Checking permission."); 118 // BEGIN_INCLUDE(camera_permission) 119 // Check if the Camera permission is already available. 120 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 121 != PackageManager.PERMISSION_GRANTED) { 122 // Camera permission has not been granted. 123 124 requestCameraPermission(); 125 126 } else { 127 128 // Camera permissions is already available, show the camera preview. 129 Log.i(TAG, 130 "CAMERA permission has already been granted. Displaying camera preview."); 131 showCameraPreview(); 132 } 133 // END_INCLUDE(camera_permission) 134 135 } 136 137 /** 138 * Requests the Camera permission. 139 * If the permission has been denied previously, a SnackBar will prompt the user to grant the 140 * permission, otherwise it is requested directly. 141 */ requestCameraPermission()142 private void requestCameraPermission() { 143 Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission."); 144 145 // BEGIN_INCLUDE(camera_permission_request) 146 if (ActivityCompat.shouldShowRequestPermissionRationale(this, 147 Manifest.permission.CAMERA)) { 148 // Provide an additional rationale to the user if the permission was not granted 149 // and the user would benefit from additional context for the use of the permission. 150 // For example if the user has previously denied the permission. 151 Log.i(TAG, 152 "Displaying camera permission rationale to provide additional context."); 153 Snackbar.make(mLayout, R.string.permission_camera_rationale, 154 Snackbar.LENGTH_INDEFINITE) 155 .setAction(R.string.ok, new View.OnClickListener() { 156 @Override 157 public void onClick(View view) { 158 ActivityCompat.requestPermissions(MainActivity.this, 159 new String[]{Manifest.permission.CAMERA}, 160 REQUEST_CAMERA); 161 } 162 }) 163 .show(); 164 } else { 165 166 // Camera permission has not been granted yet. Request it directly. 167 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 168 REQUEST_CAMERA); 169 } 170 // END_INCLUDE(camera_permission_request) 171 } 172 173 /** 174 * Called when the 'show camera' button is clicked. 175 * Callback is defined in resource layout definition. 176 */ showContacts(View v)177 public void showContacts(View v) { 178 Log.i(TAG, "Show contacts button pressed. Checking permissions."); 179 180 // Verify that all required contact permissions have been granted. 181 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) 182 != PackageManager.PERMISSION_GRANTED 183 || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) 184 != PackageManager.PERMISSION_GRANTED) { 185 // Contacts permissions have not been granted. 186 Log.i(TAG, "Contact permissions has NOT been granted. Requesting permissions."); 187 requestContactsPermissions(); 188 189 } else { 190 191 // Contact permissions have been granted. Show the contacts fragment. 192 Log.i(TAG, 193 "Contact permissions have already been granted. Displaying contact details."); 194 showContactDetails(); 195 } 196 } 197 198 /** 199 * Requests the Contacts permissions. 200 * If the permission has been denied previously, a SnackBar will prompt the user to grant the 201 * permission, otherwise it is requested directly. 202 */ requestContactsPermissions()203 private void requestContactsPermissions() { 204 // BEGIN_INCLUDE(contacts_permission_request) 205 if (ActivityCompat.shouldShowRequestPermissionRationale(this, 206 Manifest.permission.READ_CONTACTS) 207 || ActivityCompat.shouldShowRequestPermissionRationale(this, 208 Manifest.permission.WRITE_CONTACTS)) { 209 210 // Provide an additional rationale to the user if the permission was not granted 211 // and the user would benefit from additional context for the use of the permission. 212 // For example, if the request has been denied previously. 213 Log.i(TAG, 214 "Displaying contacts permission rationale to provide additional context."); 215 216 // Display a SnackBar with an explanation and a button to trigger the request. 217 Snackbar.make(mLayout, R.string.permission_contacts_rationale, 218 Snackbar.LENGTH_INDEFINITE) 219 .setAction(R.string.ok, new View.OnClickListener() { 220 @Override 221 public void onClick(View view) { 222 ActivityCompat 223 .requestPermissions(MainActivity.this, PERMISSIONS_CONTACT, 224 REQUEST_CONTACTS); 225 } 226 }) 227 .show(); 228 } else { 229 // Contact permissions have not been granted yet. Request them directly. 230 ActivityCompat.requestPermissions(this, PERMISSIONS_CONTACT, REQUEST_CONTACTS); 231 } 232 // END_INCLUDE(contacts_permission_request) 233 } 234 235 236 /** 237 * Display the {@link CameraPreviewFragment} in the content area if the required Camera 238 * permission has been granted. 239 */ showCameraPreview()240 private void showCameraPreview() { 241 getSupportFragmentManager().beginTransaction() 242 .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance()) 243 .addToBackStack("contacts") 244 .commit(); 245 } 246 247 /** 248 * Display the {@link ContactsFragment} in the content area if the required contacts 249 * permissions 250 * have been granted. 251 */ showContactDetails()252 private void showContactDetails() { 253 getSupportFragmentManager().beginTransaction() 254 .replace(R.id.sample_content_fragment, ContactsFragment.newInstance()) 255 .addToBackStack("contacts") 256 .commit(); 257 } 258 259 260 /** 261 * Callback received when a permissions request has been completed. 262 */ 263 @Override onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)264 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 265 @NonNull int[] grantResults) { 266 267 if (requestCode == REQUEST_CAMERA) { 268 // BEGIN_INCLUDE(permission_result) 269 // Received permission result for camera permission. 270 Log.i(TAG, "Received response for Camera permission request."); 271 272 // Check if the only required permission has been granted 273 if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 274 // Camera permission has been granted, preview can be displayed 275 Log.i(TAG, "CAMERA permission has now been granted. Showing preview."); 276 Snackbar.make(mLayout, R.string.permision_available_camera, 277 Snackbar.LENGTH_SHORT).show(); 278 } else { 279 Log.i(TAG, "CAMERA permission was NOT granted."); 280 Snackbar.make(mLayout, R.string.permissions_not_granted, 281 Snackbar.LENGTH_SHORT).show(); 282 283 } 284 // END_INCLUDE(permission_result) 285 286 } else if (requestCode == REQUEST_CONTACTS) { 287 Log.i(TAG, "Received response for contact permissions request."); 288 289 // We have requested multiple permissions for contacts, so all of them need to be 290 // checked. 291 if (PermissionUtil.verifyPermissions(grantResults)) { 292 // All required permissions have been granted, display contacts fragment. 293 Snackbar.make(mLayout, R.string.permision_available_contacts, 294 Snackbar.LENGTH_SHORT) 295 .show(); 296 } else { 297 Log.i(TAG, "Contacts permissions were NOT granted."); 298 Snackbar.make(mLayout, R.string.permissions_not_granted, 299 Snackbar.LENGTH_SHORT) 300 .show(); 301 } 302 303 } else { 304 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 305 } 306 } 307 308 /* Note: Methods and definitions below are only used to provide the UI for this sample and are 309 not relevant for the execution of the runtime permissions API. */ 310 311 312 @Override onCreateOptionsMenu(Menu menu)313 public boolean onCreateOptionsMenu(Menu menu) { 314 getMenuInflater().inflate(R.menu.main, menu); 315 return true; 316 } 317 318 @Override onPrepareOptionsMenu(Menu menu)319 public boolean onPrepareOptionsMenu(Menu menu) { 320 MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); 321 logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator); 322 logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log); 323 324 return super.onPrepareOptionsMenu(menu); 325 } 326 327 @Override onOptionsItemSelected(MenuItem item)328 public boolean onOptionsItemSelected(MenuItem item) { 329 switch (item.getItemId()) { 330 case R.id.menu_toggle_log: 331 mLogShown = !mLogShown; 332 ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output); 333 if (mLogShown) { 334 output.setDisplayedChild(1); 335 } else { 336 output.setDisplayedChild(0); 337 } 338 supportInvalidateOptionsMenu(); 339 return true; 340 } 341 return super.onOptionsItemSelected(item); 342 } 343 344 /** Create a chain of targets that will receive log data */ 345 @Override initializeLogging()346 public void initializeLogging() { 347 // Wraps Android's native log framework. 348 LogWrapper logWrapper = new LogWrapper(); 349 // Using Log, front-end to the logging chain, emulates android.util.log method signatures. 350 Log.setLogNode(logWrapper); 351 352 // Filter strips out everything except the message text. 353 MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); 354 logWrapper.setNext(msgFilter); 355 356 // On screen logging via a fragment with a TextView. 357 LogFragment logFragment = (LogFragment) getSupportFragmentManager() 358 .findFragmentById(R.id.log_fragment); 359 msgFilter.setNext(logFragment.getLogView()); 360 } 361 onBackClick(View view)362 public void onBackClick(View view) { 363 getSupportFragmentManager().popBackStack(); 364 } 365 366 @Override onCreate(Bundle savedInstanceState)367 protected void onCreate(Bundle savedInstanceState) { 368 super.onCreate(savedInstanceState); 369 setContentView(R.layout.activity_main); 370 mLayout = findViewById(R.id.sample_main_layout); 371 372 if (savedInstanceState == null) { 373 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 374 RuntimePermissionsFragment fragment = new RuntimePermissionsFragment(); 375 transaction.replace(R.id.sample_content_fragment, fragment); 376 transaction.commit(); 377 } 378 379 // This method sets up our custom logger, which will print all log messages to the device 380 // screen, as well as to adb logcat. 381 initializeLogging(); 382 } 383 } 384