1 /* 2 * Copyright (C) 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.android.dialer.util; 18 19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 20 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 21 import static android.Manifest.permission.ADD_VOICEMAIL; 22 import static android.Manifest.permission.CALL_PHONE; 23 import static android.Manifest.permission.MODIFY_PHONE_STATE; 24 import static android.Manifest.permission.READ_CALL_LOG; 25 import static android.Manifest.permission.READ_CONTACTS; 26 import static android.Manifest.permission.READ_PHONE_STATE; 27 import static android.Manifest.permission.READ_VOICEMAIL; 28 import static android.Manifest.permission.SEND_SMS; 29 import static android.Manifest.permission.WRITE_CALL_LOG; 30 import static android.Manifest.permission.WRITE_CONTACTS; 31 import static android.Manifest.permission.WRITE_VOICEMAIL; 32 33 import android.Manifest.permission; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.content.pm.PackageManager; 39 import android.support.annotation.NonNull; 40 import android.support.annotation.VisibleForTesting; 41 import android.support.v4.content.ContextCompat; 42 import android.support.v4.content.LocalBroadcastManager; 43 import android.widget.Toast; 44 import com.android.dialer.common.LogUtil; 45 import com.android.dialer.storage.StorageComponent; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collections; 49 import java.util.List; 50 51 /** Utility class to help with runtime permissions. */ 52 public class PermissionsUtil { 53 54 @VisibleForTesting 55 public static final String PREFERENCE_CAMERA_ALLOWED_BY_USER = "camera_allowed_by_user"; 56 57 private static final String PERMISSION_PREFERENCE = "dialer_permissions"; 58 private static final String CEQUINT_PERMISSION = "com.cequint.ecid.CALLER_ID_LOOKUP"; 59 60 // Permissions list retrieved from application manifest. 61 // Starting in Android O Permissions must be explicitly enumerated: 62 // https://developer.android.com/preview/behavior-changes.html#rmp 63 public static final List<String> allPhoneGroupPermissionsUsedInDialer = 64 Collections.unmodifiableList( 65 Arrays.asList( 66 READ_CALL_LOG, 67 WRITE_CALL_LOG, 68 READ_PHONE_STATE, 69 MODIFY_PHONE_STATE, 70 SEND_SMS, 71 CALL_PHONE, 72 ADD_VOICEMAIL, 73 WRITE_VOICEMAIL, 74 READ_VOICEMAIL)); 75 76 public static final List<String> allContactsGroupPermissionsUsedInDialer = 77 Collections.unmodifiableList(Arrays.asList(READ_CONTACTS, WRITE_CONTACTS)); 78 79 public static final List<String> allLocationGroupPermissionsUsedInDialer = 80 Collections.unmodifiableList(Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)); 81 hasPhonePermissions(Context context)82 public static boolean hasPhonePermissions(Context context) { 83 return hasPermission(context, permission.CALL_PHONE); 84 } 85 hasReadPhoneStatePermissions(Context context)86 public static boolean hasReadPhoneStatePermissions(Context context) { 87 return hasPermission(context, permission.READ_PHONE_STATE); 88 } 89 hasContactsReadPermissions(Context context)90 public static boolean hasContactsReadPermissions(Context context) { 91 return hasPermission(context, permission.READ_CONTACTS); 92 } 93 hasContactsWritePermissions(Context context)94 public static boolean hasContactsWritePermissions(Context context) { 95 return hasPermission(context, permission.WRITE_CONTACTS); 96 } 97 hasLocationPermissions(Context context)98 public static boolean hasLocationPermissions(Context context) { 99 return hasPermission(context, permission.ACCESS_FINE_LOCATION); 100 } 101 hasCameraPermissions(Context context)102 public static boolean hasCameraPermissions(Context context) { 103 return hasPermission(context, permission.CAMERA); 104 } 105 hasMicrophonePermissions(Context context)106 public static boolean hasMicrophonePermissions(Context context) { 107 return hasPermission(context, permission.RECORD_AUDIO); 108 } 109 hasCallLogReadPermissions(Context context)110 public static boolean hasCallLogReadPermissions(Context context) { 111 return hasPermission(context, permission.READ_CALL_LOG); 112 } 113 hasCallLogWritePermissions(Context context)114 public static boolean hasCallLogWritePermissions(Context context) { 115 return hasPermission(context, permission.WRITE_CALL_LOG); 116 } 117 hasCequintPermissions(Context context)118 public static boolean hasCequintPermissions(Context context) { 119 return hasPermission(context, CEQUINT_PERMISSION); 120 } 121 hasReadVoicemailPermissions(Context context)122 public static boolean hasReadVoicemailPermissions(Context context) { 123 return hasPermission(context, permission.READ_VOICEMAIL); 124 } 125 hasWriteVoicemailPermissions(Context context)126 public static boolean hasWriteVoicemailPermissions(Context context) { 127 return hasPermission(context, permission.WRITE_VOICEMAIL); 128 } 129 hasAddVoicemailPermissions(Context context)130 public static boolean hasAddVoicemailPermissions(Context context) { 131 return hasPermission(context, permission.ADD_VOICEMAIL); 132 } 133 hasSendSmsPermissions(Context context)134 public static boolean hasSendSmsPermissions(Context context) { 135 return hasPermission(context, permission.SEND_SMS); 136 } 137 hasPermission(Context context, String permission)138 public static boolean hasPermission(Context context, String permission) { 139 return ContextCompat.checkSelfPermission(context, permission) 140 == PackageManager.PERMISSION_GRANTED; 141 } 142 143 /** 144 * Checks {@link android.content.SharedPreferences} if a permission has been requested before. 145 * 146 * <p>It is important to note that this method only works if you call {@link 147 * PermissionsUtil#permissionRequested(Context, String)} in {@link 148 * android.app.Activity#onRequestPermissionsResult(int, String[], int[])}. 149 */ isFirstRequest(Context context, String permission)150 public static boolean isFirstRequest(Context context, String permission) { 151 return context 152 .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) 153 .getBoolean(permission, true); 154 } 155 156 /** 157 * Records in {@link android.content.SharedPreferences} that the specified permission has been 158 * requested at least once. 159 * 160 * <p>This method should be called in {@link android.app.Activity#onRequestPermissionsResult(int, 161 * String[], int[])}. 162 */ permissionRequested(Context context, String permission)163 public static void permissionRequested(Context context, String permission) { 164 context 165 .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) 166 .edit() 167 .putBoolean(permission, false) 168 .apply(); 169 } 170 171 /** 172 * Rudimentary methods wrapping the use of a LocalBroadcastManager to simplify the process of 173 * notifying other classes when a particular fragment is notified that a permission is granted. 174 * 175 * <p>To be notified when a permission has been granted, create a new broadcast receiver and 176 * register it using {@link #registerPermissionReceiver(Context, BroadcastReceiver, String)} 177 * 178 * <p>E.g. 179 * 180 * <p>final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void 181 * onReceive(Context context, Intent intent) { refreshContactsView(); } } 182 * 183 * <p>PermissionsUtil.registerPermissionReceiver(getActivity(), receiver, READ_CONTACTS); 184 * 185 * <p>If you register to listen for multiple permissions, you can identify which permission was 186 * granted by inspecting {@link Intent#getAction()}. 187 * 188 * <p>In the fragment that requests for the permission, be sure to call {@link 189 * #notifyPermissionGranted(Context, String)} when the permission is granted so that any 190 * interested listeners are notified of the change. 191 */ registerPermissionReceiver( Context context, BroadcastReceiver receiver, String permission)192 public static void registerPermissionReceiver( 193 Context context, BroadcastReceiver receiver, String permission) { 194 LogUtil.i("PermissionsUtil.registerPermissionReceiver", permission); 195 final IntentFilter filter = new IntentFilter(permission); 196 LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter); 197 } 198 unregisterPermissionReceiver(Context context, BroadcastReceiver receiver)199 public static void unregisterPermissionReceiver(Context context, BroadcastReceiver receiver) { 200 LogUtil.i("PermissionsUtil.unregisterPermissionReceiver", null); 201 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver); 202 } 203 notifyPermissionGranted(Context context, String permission)204 public static void notifyPermissionGranted(Context context, String permission) { 205 LogUtil.i("PermissionsUtil.notifyPermissionGranted", permission); 206 final Intent intent = new Intent(permission); 207 LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 208 } 209 210 /** 211 * Returns a list of permissions currently not granted to the application from the supplied list. 212 * 213 * @param context - The Application context. 214 * @param permissionsList - A list of permissions to check if the current application has been 215 * granted. 216 * @return An array of permissions that are currently DENIED to the application; a subset of 217 * permissionsList. 218 */ 219 @NonNull getPermissionsCurrentlyDenied( @onNull Context context, @NonNull List<String> permissionsList)220 public static String[] getPermissionsCurrentlyDenied( 221 @NonNull Context context, @NonNull List<String> permissionsList) { 222 List<String> permissionsCurrentlyDenied = new ArrayList<>(); 223 for (String permission : permissionsList) { 224 if (!hasPermission(context, permission)) { 225 permissionsCurrentlyDenied.add(permission); 226 } 227 } 228 return permissionsCurrentlyDenied.toArray(new String[permissionsCurrentlyDenied.size()]); 229 } 230 231 /** 232 * Since we are granted the camera permission automatically as a first-party app, we need to show 233 * a toast to let users know the permission was granted for privacy reasons. 234 * 235 * @return true if we've already shown the camera privacy toast. 236 */ hasCameraPrivacyToastShown(@onNull Context context)237 public static boolean hasCameraPrivacyToastShown(@NonNull Context context) { 238 return StorageComponent.get(context) 239 .unencryptedSharedPrefs() 240 .getBoolean(PREFERENCE_CAMERA_ALLOWED_BY_USER, false); 241 } 242 showCameraPermissionToast(@onNull Context context)243 public static void showCameraPermissionToast(@NonNull Context context) { 244 Toast.makeText(context, context.getString(R.string.camera_privacy_text), Toast.LENGTH_LONG) 245 .show(); 246 setCameraPrivacyToastShown(context); 247 } 248 setCameraPrivacyToastShown(@onNull Context context)249 public static void setCameraPrivacyToastShown(@NonNull Context context) { 250 StorageComponent.get(context) 251 .unencryptedSharedPrefs() 252 .edit() 253 .putBoolean(PREFERENCE_CAMERA_ALLOWED_BY_USER, true) 254 .apply(); 255 } 256 } 257