1 package com.google.phonenumbers.demoapp.contacts; 2 3 import static android.content.Context.MODE_PRIVATE; 4 5 import android.Manifest.permission; 6 import android.app.Activity; 7 import android.content.Context; 8 import android.content.Intent; 9 import android.content.SharedPreferences; 10 import android.content.pm.PackageManager; 11 import android.net.Uri; 12 import androidx.core.app.ActivityCompat; 13 import androidx.core.content.ContextCompat; 14 15 /** 16 * Handles everything related to the contacts permissions ({@link permission#READ_CONTACTS} and 17 * {@link permission#WRITE_CONTACTS}) and the requesting process to grant the permissions. 18 */ 19 public class ContactsPermissionManagement { 20 21 public static final int CONTACTS_PERMISSION_REQUEST_CODE = 0; 22 23 private static final String SHARED_PREFS_NAME = "contacts-permission-management"; 24 private static final String NUMBER_OF_CONTACTS_PERMISSION_DENIALS_KEY = 25 "NUMBER_OF_CONTACTS_PERMISSION_DENIALS"; 26 ContactsPermissionManagement()27 private ContactsPermissionManagement() {} 28 29 /** 30 * Returns the current state of the permissions granting as {@link PermissionState}. 31 * 32 * @param activity Activity of the app 33 * @return {@link PermissionState} of the permissions granting 34 */ getState(Activity activity)35 public static PermissionState getState(Activity activity) { 36 if (isGranted(activity.getApplicationContext())) { 37 return PermissionState.ALREADY_GRANTED; 38 } 39 if (!shouldPermissionBeRequestedInApp(activity.getApplicationContext())) { 40 return PermissionState.NEEDS_GRANT_IN_SETTINGS; 41 } 42 if (shouldShowRationale(activity)) { 43 return PermissionState.SHOW_RATIONALE; 44 } 45 return PermissionState.NEEDS_REQUEST; 46 } 47 48 /** 49 * Returns whether the contacts permissions ({@link permission#READ_CONTACTS} and {@link 50 * permission#WRITE_CONTACTS}) are granted for the param {@code context}. 51 * 52 * @param context Context of the app 53 * @return boolean whether contacts permissions are granted 54 */ isGranted(Context context)55 public static boolean isGranted(Context context) { 56 if (ContextCompat.checkSelfPermission(context, permission.READ_CONTACTS) 57 == PackageManager.PERMISSION_DENIED) { 58 return false; 59 } 60 return ContextCompat.checkSelfPermission(context, permission.WRITE_CONTACTS) 61 != PackageManager.PERMISSION_DENIED; 62 } 63 64 /** 65 * Returns whether the permissions should be requested directly in the app or not. Specifically 66 * returns true if less than 2 denials happened since the app installation. 67 * 68 * @param context Context of the app 69 * @return boolean whether the permissions should be requested directly in the app 70 */ shouldPermissionBeRequestedInApp(Context context)71 private static boolean shouldPermissionBeRequestedInApp(Context context) { 72 return getNumberOfDenials(context) < 2; 73 } 74 75 /** 76 * Returns the number of times the permission dialog has been denied since the app installation. 77 * Dismissing the permission dialog instead of answering is considered a denial. 78 * 79 * @param context Context of the app 80 * @return int number of times the permission has been denied 81 */ getNumberOfDenials(Context context)82 private static int getNumberOfDenials(Context context) { 83 SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); 84 return preferences.getInt(NUMBER_OF_CONTACTS_PERMISSION_DENIALS_KEY, 0); 85 } 86 87 /** 88 * Adds 1 to the number of denials since the app installation. Should be called every time the 89 * user denies the permission (in the dialog). Dismissing the permission dialog instead of 90 * answering is considered a denial. 91 * 92 * @param context Context of the app 93 */ addOneToNumberOfDenials(Context context)94 public static void addOneToNumberOfDenials(Context context) { 95 SharedPreferences.Editor editor = 96 context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE).edit(); 97 editor.putInt(NUMBER_OF_CONTACTS_PERMISSION_DENIALS_KEY, getNumberOfDenials(context) + 1); 98 editor.apply(); 99 } 100 101 /** 102 * Returns whether a rational should be shown explaining why the app requests these permissions 103 * (before requesting them). 104 * 105 * @param activity Activity of the app 106 * @return boolean whether a rational should be shown 107 */ shouldShowRationale(Activity activity)108 private static boolean shouldShowRationale(Activity activity) { 109 if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission.READ_CONTACTS)) { 110 return true; 111 } 112 return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission.WRITE_CONTACTS); 113 } 114 115 /** 116 * Requests the contact permissions ({@link permission#READ_CONTACTS} and {@link 117 * permission#WRITE_CONTACTS}) in the param {@code activity} with the request code {@link 118 * ContactsPermissionManagement#CONTACTS_PERMISSION_REQUEST_CODE}. 119 * 120 * @param activity Activity of the app 121 */ request(Activity activity)122 public static void request(Activity activity) { 123 activity.requestPermissions( 124 new String[] {permission.READ_CONTACTS, permission.WRITE_CONTACTS}, 125 CONTACTS_PERMISSION_REQUEST_CODE); 126 } 127 128 /** 129 * Opens the system settings (app details page) if the app can. Special cases that can not open 130 * the system settings are for example emulators without Play Store installed. 131 * 132 * @param activity Activity of the app 133 */ openSystemSettings(Activity activity)134 public static void openSystemSettings(Activity activity) { 135 Intent intent = 136 new Intent( 137 android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 138 Uri.parse("package:" + activity.getPackageName())); 139 activity.startActivity(intent); 140 } 141 142 /** Represents the different states the permissions granting process can be at. */ 143 public enum PermissionState { 144 /** The permissions are already granted. The action requiring the permissions can be started. */ 145 ALREADY_GRANTED, 146 /** 147 * The permissions are not granted, but can be requested directly (without showing a rationale). 148 */ 149 NEEDS_REQUEST, 150 /** 151 * The permissions are not granted and a rationale should be shown explaining why the app 152 * requests the permissions before requesting them (directly in the app). 153 */ 154 SHOW_RATIONALE, 155 /** 156 * The permissions are not granted and can not be granted directly in the app. The user has to 157 * grant permissions in the system settings instead. 158 */ 159 NEEDS_GRANT_IN_SETTINGS 160 } 161 } 162