Handle some runtime permissions in ContactsCommon

Add utility methods to check if a permission group has been
granted at runtime.

Perform checks before executing certain functionality (CountryDetector,
Clear frequents dialog, preload of photos in ContactPhotoManager).

Bug: 20266292

Change-Id: I0dd3d82d69780b7d3243579eb5b09f96104c1c1f
diff --git a/src/com/android/contacts/common/ContactPhotoManager.java b/src/com/android/contacts/common/ContactPhotoManager.java
index 87ac044..dce6a77 100644
--- a/src/com/android/contacts/common/ContactPhotoManager.java
+++ b/src/com/android/contacts/common/ContactPhotoManager.java
@@ -56,6 +56,7 @@
 
 import com.android.contacts.common.lettertiles.LetterTileDrawable;
 import com.android.contacts.common.util.BitmapUtil;
+import com.android.contacts.common.util.PermissionsUtil;
 import com.android.contacts.common.util.UriUtils;
 import com.android.contacts.commonbind.util.UserAgentGenerator;
 
@@ -434,7 +435,9 @@
             Context applicationContext = context.getApplicationContext();
             sInstance = createContactPhotoManager(applicationContext);
             applicationContext.registerComponentCallbacks(sInstance);
-            sInstance.preloadPhotosInBackground();
+            if (PermissionsUtil.hasContactsPermissions(context)) {
+                sInstance.preloadPhotosInBackground();
+            }
         }
         return sInstance;
     }
diff --git a/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java
index 2cfd36e..2fab3e1 100644
--- a/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java
+++ b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java
@@ -21,6 +21,7 @@
 import android.app.DialogFragment;
 import android.app.FragmentManager;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.os.AsyncTask;
@@ -28,6 +29,7 @@
 import android.provider.ContactsContract;
 
 import com.android.contacts.common.R;
+import com.android.contacts.common.util.PermissionsUtil;
 
 /**
  * Dialog that clears the frequently contacted list after confirming with the user.
@@ -41,10 +43,14 @@
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Context context = getActivity().getApplicationContext();
         final ContentResolver resolver = getActivity().getContentResolver();
         final OnClickListener okListener = new OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
+                if (!PermissionsUtil.hasContactsPermissions(context)) {
+                    return;
+                }
                 final IndeterminateProgressDialog progressDialog = IndeterminateProgressDialog.show(
                         getFragmentManager(), getString(R.string.clearFrequentsProgress_title),
                         null, 500);
diff --git a/src/com/android/contacts/common/location/CountryDetector.java b/src/com/android/contacts/common/location/CountryDetector.java
index 7ad57d2..129effd 100644
--- a/src/com/android/contacts/common/location/CountryDetector.java
+++ b/src/com/android/contacts/common/location/CountryDetector.java
@@ -11,8 +11,10 @@
 import android.preference.PreferenceManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.contacts.common.testing.NeededForTesting;
+import com.android.contacts.common.util.PermissionsUtil;
 
 import java.util.Locale;
 
@@ -91,6 +93,11 @@
 
     public static void registerForLocationUpdates(Context context,
             LocationManager locationManager) {
+        if (!PermissionsUtil.hasLocationPermissions(context)) {
+            Log.w(TAG, "No location permissions, not registering for location updates.");
+            return;
+        }
+
         if (!Geocoder.isPresent()) {
             // Certain devices do not have an implementation of a geocoder - in that case there is
             // no point trying to get location updates because we cannot retrieve the country based
@@ -158,7 +165,7 @@
      * @return the geocoded country code detected by the {@link LocationManager}.
      */
     private String getLocationBasedCountryIso() {
-        if (!Geocoder.isPresent()) {
+        if (!Geocoder.isPresent() || !PermissionsUtil.hasLocationPermissions(mContext)) {
             return null;
         }
         final SharedPreferences sharedPreferences =
diff --git a/src/com/android/contacts/common/util/PermissionsUtil.java b/src/com/android/contacts/common/util/PermissionsUtil.java
new file mode 100644
index 0000000..97f1cf6
--- /dev/null
+++ b/src/com/android/contacts/common/util/PermissionsUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.common.util;
+
+import android.Manifest.permission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * Utility class to help with runtime permissions.
+ */
+public class PermissionsUtil {
+    // Each permission in this list is a cherry-picked permission from a particular permission
+    // group. Granting a permission group enables access to all permissions in that group so we
+    // only need to check a single permission in each group.
+    // Note: This assumes that the app has correctly requested for all the relevant permissions
+    // in its Manifest file.
+    public static final String PHONE = permission.CALL_PHONE;
+    public static final String CONTACTS = permission.READ_CONTACTS;
+    public static final String LOCATION = permission.ACCESS_FINE_LOCATION;
+
+    private static Boolean sHasPhonePermissions;
+    private static Boolean sHasContactsPermissions;
+    private static Boolean sHasLocationPermissions;
+
+    public static boolean hasPhonePermissions(Context context) {
+        if (sHasPhonePermissions == null) {
+            sHasPhonePermissions = hasPermission(context, PHONE);
+        }
+        return sHasPhonePermissions;
+    }
+
+    public static boolean hasContactsPermissions(Context context) {
+        if (sHasContactsPermissions == null) {
+            sHasContactsPermissions = hasPermission(context, CONTACTS);
+        }
+        return sHasContactsPermissions;
+    }
+
+    public static boolean hasLocationPermissions(Context context) {
+        if (sHasLocationPermissions == null) {
+            sHasLocationPermissions = hasPermission(context, LOCATION);
+        }
+        return sHasLocationPermissions;
+    }
+
+    /**
+     * To be called during various activity lifecycle events to update the cached versions of the
+     * permissions.
+     *
+     * @param context A valid context.
+     */
+    public static void updateCachedPermissions(Context context) {
+        hasPermission(context, PHONE);
+        hasPermission(context, CONTACTS);
+        hasPermission(context, LOCATION);
+    }
+
+    public static boolean hasPermission(Context context, String permission) {
+        return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
+    }
+}