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 82 public static boolean hasPhonePermissions(Context context) { 83 return hasPermission(context, permission.CALL_PHONE); 84 } 85 86 public static boolean hasReadPhoneStatePermissions(Context context) { 87 return hasPermission(context, permission.READ_PHONE_STATE); 88 } 89 90 public static boolean hasContactsReadPermissions(Context context) { 91 return hasPermission(context, permission.READ_CONTACTS); 92 } 93 94 public static boolean hasLocationPermissions(Context context) { 95 return hasPermission(context, permission.ACCESS_FINE_LOCATION); 96 } 97 98 public static boolean hasCameraPermissions(Context context) { 99 return hasPermission(context, permission.CAMERA); 100 } 101 102 public static boolean hasMicrophonePermissions(Context context) { 103 return hasPermission(context, permission.RECORD_AUDIO); 104 } 105 106 public static boolean hasCallLogReadPermissions(Context context) { 107 return hasPermission(context, permission.READ_CALL_LOG); 108 } 109 110 public static boolean hasCallLogWritePermissions(Context context) { 111 return hasPermission(context, permission.WRITE_CALL_LOG); 112 } 113 114 public static boolean hasCequintPermissions(Context context) { 115 return hasPermission(context, CEQUINT_PERMISSION); 116 } 117 118 public static boolean hasReadVoicemailPermissions(Context context) { 119 return hasPermission(context, permission.READ_VOICEMAIL); 120 } 121 122 public static boolean hasWriteVoicemailPermissions(Context context) { 123 return hasPermission(context, permission.WRITE_VOICEMAIL); 124 } 125 126 public static boolean hasAddVoicemailPermissions(Context context) { 127 return hasPermission(context, permission.ADD_VOICEMAIL); 128 } 129 130 public static boolean hasPermission(Context context, String permission) { 131 return ContextCompat.checkSelfPermission(context, permission) 132 == PackageManager.PERMISSION_GRANTED; 133 } 134 135 /** 136 * Checks {@link android.content.SharedPreferences} if a permission has been requested before. 137 * 138 * <p>It is important to note that this method only works if you call {@link 139 * PermissionsUtil#permissionRequested(Context, String)} in {@link 140 * android.app.Activity#onRequestPermissionsResult(int, String[], int[])}. 141 */ 142 public static boolean isFirstRequest(Context context, String permission) { 143 return context 144 .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) 145 .getBoolean(permission, true); 146 } 147 148 /** 149 * Records in {@link android.content.SharedPreferences} that the specified permission has been 150 * requested at least once. 151 * 152 * <p>This method should be called in {@link android.app.Activity#onRequestPermissionsResult(int, 153 * String[], int[])}. 154 */ 155 public static void permissionRequested(Context context, String permission) { 156 context 157 .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) 158 .edit() 159 .putBoolean(permission, false) 160 .apply(); 161 } 162 163 /** 164 * Rudimentary methods wrapping the use of a LocalBroadcastManager to simplify the process of 165 * notifying other classes when a particular fragment is notified that a permission is granted. 166 * 167 * <p>To be notified when a permission has been granted, create a new broadcast receiver and 168 * register it using {@link #registerPermissionReceiver(Context, BroadcastReceiver, String)} 169 * 170 * <p>E.g. 171 * 172 * <p>final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void 173 * onReceive(Context context, Intent intent) { refreshContactsView(); } } 174 * 175 * <p>PermissionsUtil.registerPermissionReceiver(getActivity(), receiver, READ_CONTACTS); 176 * 177 * <p>If you register to listen for multiple permissions, you can identify which permission was 178 * granted by inspecting {@link Intent#getAction()}. 179 * 180 * <p>In the fragment that requests for the permission, be sure to call {@link 181 * #notifyPermissionGranted(Context, String)} when the permission is granted so that any 182 * interested listeners are notified of the change. 183 */ 184 public static void registerPermissionReceiver( 185 Context context, BroadcastReceiver receiver, String permission) { 186 LogUtil.i("PermissionsUtil.registerPermissionReceiver", permission); 187 final IntentFilter filter = new IntentFilter(permission); 188 LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter); 189 } 190 191 public static void unregisterPermissionReceiver(Context context, BroadcastReceiver receiver) { 192 LogUtil.i("PermissionsUtil.unregisterPermissionReceiver", null); 193 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver); 194 } 195 196 public static void notifyPermissionGranted(Context context, String permission) { 197 LogUtil.i("PermissionsUtil.notifyPermissionGranted", permission); 198 final Intent intent = new Intent(permission); 199 LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 200 } 201 202 /** 203 * Returns a list of permissions currently not granted to the application from the supplied list. 204 * 205 * @param context - The Application context. 206 * @param permissionsList - A list of permissions to check if the current application has been 207 * granted. 208 * @return An array of permissions that are currently DENIED to the application; a subset of 209 * permissionsList. 210 */ 211 @NonNull 212 public static String[] getPermissionsCurrentlyDenied( 213 @NonNull Context context, @NonNull List<String> permissionsList) { 214 List<String> permissionsCurrentlyDenied = new ArrayList<>(); 215 for (String permission : permissionsList) { 216 if (!hasPermission(context, permission)) { 217 permissionsCurrentlyDenied.add(permission); 218 } 219 } 220 return permissionsCurrentlyDenied.toArray(new String[permissionsCurrentlyDenied.size()]); 221 } 222 223 /** 224 * Since we are granted the camera permission automatically as a first-party app, we need to show 225 * a toast to let users know the permission was granted for privacy reasons. 226 * 227 * @return true if we've already shown the camera privacy toast. 228 */ 229 public static boolean hasCameraPrivacyToastShown(@NonNull Context context) { 230 return StorageComponent.get(context) 231 .unencryptedSharedPrefs() 232 .getBoolean(PREFERENCE_CAMERA_ALLOWED_BY_USER, false); 233 } 234 235 public static void showCameraPermissionToast(@NonNull Context context) { 236 Toast.makeText(context, context.getString(R.string.camera_privacy_text), Toast.LENGTH_LONG) 237 .show(); 238 setCameraPrivacyToastShown(context); 239 } 240 241 public static void setCameraPrivacyToastShown(@NonNull Context context) { 242 StorageComponent.get(context) 243 .unencryptedSharedPrefs() 244 .edit() 245 .putBoolean(PREFERENCE_CAMERA_ALLOWED_BY_USER, true) 246 .apply(); 247 } 248 } 249