Add cache for isVoicemail check.

bindView() gets called many times at dialer startup and layout updates.
Besides being costly, it invokes cross-process TelecomManager methods.
This change addresses the cross-process issues by adding a short-lived
cache to isVoicemail() method invocations.

Change-Id: Ib69c0eb3969a1b7d77c9fd1a2aa6e578a31fb5e9
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 9c50a3b..b6f91e7 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -631,6 +631,8 @@
 
     /**
      * Binds the views in the entry to the data in the call log.
+     * TODO: This gets called 20-30 times when Dialer starts up for a single call log entry and
+     * should not. It invokes cross-process methods and the repeat execution can get costly.
      *
      * @param callLogItemView the view corresponding to this entry
      * @param c the cursor pointing to the entry in the call log
diff --git a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java b/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
index 11f4a67..6fa8143 100644
--- a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
+++ b/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
@@ -20,13 +20,15 @@
 import android.provider.CallLog;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
 
 import com.android.contacts.common.util.PhoneNumberHelper;
-
 import com.google.common.collect.Sets;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -34,8 +36,20 @@
  */
 public class PhoneNumberUtilsWrapper {
     private static final Set<String> LEGACY_UNKNOWN_NUMBERS = Sets.newHashSet("-1", "-2", "-3");
+    private static final long MAX_VOICEMAIL_CACHE_AGE_IN_MS = 60 * 1000;  // 60 seconds
     private final Context mContext;
 
+    // Keeps a cache of recently-made voicemail queries.  The entire point of this cache is to
+    // reduce the number of cross-process requests to TelecomManager.
+    // Maps from a phone-account/number pair to a boolean because multiple numbers could return true
+    // for the voicemail number if those numbers are not pre-normalized.
+    //
+    // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of
+    // this writing, that was a much larger undertaking than creating this cache.
+    private final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache =
+            new HashMap<>();
+    private long mVoicemailCacheTimestamp = 0;
+
     public PhoneNumberUtilsWrapper(Context context) {
         mContext = context;
     }
@@ -50,11 +64,34 @@
      * Returns true if the given number is the number of the configured voicemail. To be able to
      * mock-out this, it is not a static method.
      */
-    public boolean isVoicemailNumber(PhoneAccountHandle accountHandle,
-            CharSequence number) {
-        final TelecomManager telecomManager =
-                (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
-        return number!= null && telecomManager.isVoiceMailNumber(accountHandle, number.toString());
+    public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
+        if (TextUtils.isEmpty(number)) {
+            return false;
+        }
+
+        long currentTime = System.currentTimeMillis();
+        // check the age of the voicemail cache first.
+        if (currentTime - mVoicemailCacheTimestamp > MAX_VOICEMAIL_CACHE_AGE_IN_MS) {
+            mVoicemailQueryCache.clear();
+
+            // We set the timestamp of the voicemail cache to the point where the cache is recreated
+            // instead of when an item is added.
+            // 1) This is easier to write
+            // 2) Ensures that the oldest entry is never older than MAX_VOICEMAIL_CACHE_AGE
+            mVoicemailCacheTimestamp = currentTime;
+        }
+
+        Pair<PhoneAccountHandle, CharSequence> key = new Pair<>(accountHandle, number);
+        if (mVoicemailQueryCache.containsKey(key)) {
+            return mVoicemailQueryCache.get(key);
+        } else {
+            final TelecomManager telecomManager =
+                    (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+            Boolean isVoicemail =
+                    telecomManager.isVoiceMailNumber(accountHandle, number.toString());
+            mVoicemailQueryCache.put(key, isVoicemail);
+            return isVoicemail;
+        }
     }
 
     /**