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