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.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 
     76   public static boolean hasPhonePermissions(Context context) {
     77     return hasPermission(context, permission.CALL_PHONE);
     78   }
     79 
     80   public static boolean hasContactsReadPermissions(Context context) {
     81     return hasPermission(context, permission.READ_CONTACTS);
     82   }
     83 
     84   public static boolean hasLocationPermissions(Context context) {
     85     return hasPermission(context, permission.ACCESS_FINE_LOCATION);
     86   }
     87 
     88   public static boolean hasCameraPermissions(Context context) {
     89     return hasPermission(context, permission.CAMERA);
     90   }
     91 
     92   public static boolean hasMicrophonePermissions(Context context) {
     93     return hasPermission(context, permission.RECORD_AUDIO);
     94   }
     95 
     96   public static boolean hasCallLogReadPermissions(Context context) {
     97     return hasPermission(context, permission.READ_CALL_LOG);
     98   }
     99 
    100   public static boolean hasCallLogWritePermissions(Context context) {
    101     return hasPermission(context, permission.WRITE_CALL_LOG);
    102   }
    103 
    104   public static boolean hasCequintPermissions(Context context) {
    105     return hasPermission(context, CEQUINT_PERMISSION);
    106   }
    107 
    108   public static boolean hasReadVoicemailPermissions(Context context) {
    109     return hasPermission(context, permission.READ_VOICEMAIL);
    110   }
    111 
    112   public static boolean hasWriteVoicemailPermissions(Context context) {
    113     return hasPermission(context, permission.WRITE_VOICEMAIL);
    114   }
    115 
    116   public static boolean hasAddVoicemailPermissions(Context context) {
    117     return hasPermission(context, permission.ADD_VOICEMAIL);
    118   }
    119 
    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    */
    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    */
    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    */
    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 
    181   public static void unregisterPermissionReceiver(Context context, BroadcastReceiver receiver) {
    182     LogUtil.i("PermissionsUtil.unregisterPermissionReceiver", null);
    183     LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
    184   }
    185 
    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
    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    */
    219   public static boolean hasCameraPrivacyToastShown(@NonNull Context context) {
    220     return DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(context)
    221         .getBoolean(PREFERENCE_CAMERA_ALLOWED_BY_USER, false);
    222   }
    223 
    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 
    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