Home | History | Annotate | Download | only in util
      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