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