Store contact info using country iso as well.

Currently we are using just the number to store contact information that
we look-up. However, the look-up result depends also on the country in
which the call was placed or received.

This can lead to issues if the result is not the same depending on the
contact. This is particularly problematic in case of upgrades: the call
log will contain a number of entries without a country iso value since
the field was introduced after the Gingerbread release.

Use the country iso both when storing the contact in the in-memory
cache, as well as when updating the cache stored in the call log itself.

Bug: 5638376
Change-Id: I4c77556395147f05c2524d93cc1c800943395b6c
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index b38430b..37beba3 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -54,6 +54,37 @@
         public void fetchCalls();
     }
 
+    /**
+     * Stores a phone number of a call with the country code where it originally occurred.
+     * <p>
+     * Note the country does not necessarily specifies the country of the phone number itself, but
+     * it is the country in which the user was in when the call was placed or received.
+     */
+    private static final class NumberWithCountryIso {
+        public final String number;
+        public final String countryIso;
+
+        public NumberWithCountryIso(String number, String countryIso) {
+            this.number = number;
+            this.countryIso = countryIso;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null) return false;
+            if (!(o instanceof NumberWithCountryIso)) return false;
+            NumberWithCountryIso other = (NumberWithCountryIso) o;
+            return TextUtils.equals(number, other.number)
+                    && TextUtils.equals(countryIso, other.countryIso);
+        }
+
+        @Override
+        public int hashCode() {
+            return (number == null ? 0 : number.hashCode())
+                    ^ (countryIso == null ? 0 : countryIso.hashCode());
+        }
+    }
+
     /** The time in millis to delay starting the thread processing requests. */
     private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
 
@@ -69,8 +100,10 @@
      * <p>
      * The content of the cache is expired (but not purged) whenever the application comes to
      * the foreground.
+     * <p>
+     * The key is number with the country in which the call was placed or received.
      */
-    private ExpirableCache<String, ContactInfo> mContactInfoCache;
+    private ExpirableCache<NumberWithCountryIso, ContactInfo> mContactInfoCache;
 
     /**
      * A request for contact details for the given number.
@@ -317,14 +350,15 @@
 
         // Check the existing entry in the cache: only if it has changed we should update the
         // view.
-        ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
+        NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+        ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(numberCountryIso);
         boolean updated = !info.equals(existingInfo);
         // Store the data in the cache so that the UI thread can use to display it. Store it
         // even if it has not changed so that it is marked as not expired.
-        mContactInfoCache.put(number, info);
+        mContactInfoCache.put(numberCountryIso, info);
         // Update the call log even if the cache it is up-to-date: it is possible that the cache
         // contains the value from a different call log entry.
-        updateCallLogContactInfoCache(number, info, callLogInfo);
+        updateCallLogContactInfoCache(number, countryIso, info, callLogInfo);
         return updated;
     }
     /*
@@ -471,8 +505,9 @@
         }
 
         // Lookup contacts with this number
+        NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
         ExpirableCache.CachedValue<ContactInfo> cachedInfo =
-                mContactInfoCache.getCachedValue(number);
+                mContactInfoCache.getCachedValue(numberCountryIso);
         ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
         if (!mPhoneNumberHelper.canPlaceCallsTo(number)
                 || mPhoneNumberHelper.isVoicemailNumber(number)) {
@@ -480,7 +515,7 @@
             // for it.
             info = ContactInfo.EMPTY;
         } else if (cachedInfo == null) {
-            mContactInfoCache.put(number, ContactInfo.EMPTY);
+            mContactInfoCache.put(numberCountryIso, ContactInfo.EMPTY);
             // Use the cached contact info from the call log.
             info = cachedContactInfo;
             // The db request should happen on a non-UI thread.
@@ -558,8 +593,8 @@
     }
 
     /** Stores the updated contact info in the call log if it is different from the current one. */
-    private void updateCallLogContactInfoCache(String number, ContactInfo updatedInfo,
-            ContactInfo callLogInfo) {
+    private void updateCallLogContactInfoCache(String number, String countryIso,
+            ContactInfo updatedInfo, ContactInfo callLogInfo) {
         final ContentValues values = new ContentValues();
         boolean needsUpdate = false;
 
@@ -617,10 +652,12 @@
 
         StringBuilder where = new StringBuilder();
         where.append(Calls.NUMBER);
+        where.append(" = ? AND ");
+        where.append(Calls.COUNTRY_ISO);
         where.append(" = ?");
 
         mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
-                where.toString(), new String[]{ number });
+                where.toString(), new String[]{ number, countryIso });
     }
 
     /** Returns the contact information as stored in the call log. */
@@ -674,8 +711,9 @@
     }
 
     @VisibleForTesting
-    void injectContactInfoForTest(String number, ContactInfo contactInfo) {
-        mContactInfoCache.put(number, contactInfo);
+    void injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo) {
+        NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+        mContactInfoCache.put(numberCountryIso, contactInfo);
     }
 
     @Override
@@ -692,10 +730,11 @@
      *         Else if the number in the contacts starts with a "+", use that one
      *         Else if the number in the contacts is longer, use that one
      */
-    public String getBetterNumberFromContacts(String number) {
+    public String getBetterNumberFromContacts(String number, String countryIso) {
         String matchingNumber = null;
         // Look in the cache first. If it's not found then query the Phones db
-        ContactInfo ci = mContactInfoCache.getPossiblyExpired(number);
+        NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
+        ContactInfo ci = mContactInfoCache.getPossiblyExpired(numberCountryIso);
         if (ci != null && ci != ContactInfo.EMPTY) {
             matchingNumber = ci.number;
         } else {
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 88aab4d..f14f169 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -334,7 +334,8 @@
                        (callType == Calls.INCOMING_TYPE
                                 || callType == Calls.MISSED_TYPE)) {
                     // If the caller-id matches a contact with a better qualified number, use it
-                    number = mAdapter.getBetterNumberFromContacts(number);
+                    String countryIso = cursor.getString(CallLogQuery.COUNTRY_ISO);
+                    number = mAdapter.getBetterNumberFromContacts(number, countryIso);
                 }
                 intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
                                     Uri.fromParts("tel", number, null));
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
index 600a589..8a98018 100644
--- a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
@@ -117,7 +117,7 @@
 
     public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
         mCursor.addRow(createCallLogEntry());
-        mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+        mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
 
         // Bind the views of a single row.
         mAdapter.bindStandAloneView(mView, getContext(), mCursor);
@@ -132,7 +132,7 @@
 
     public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
         mCursor.addRow(createCallLogEntryWithCachedValues());
-        mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+        mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
 
         // Bind the views of a single row.
         mAdapter.bindStandAloneView(mView, getContext(), mCursor);
@@ -147,7 +147,7 @@
         // Contact info contains a different name.
         ContactInfo info = createContactInfo();
         info.name = "new name";
-        mAdapter.injectContactInfoForTest(TEST_NUMBER, info);
+        mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, info);
 
         // Bind the views of a single row.
         mAdapter.bindStandAloneView(mView, getContext(), mCursor);
diff --git a/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java b/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java
index 09fe463..9cac8fe 100644
--- a/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java
@@ -482,7 +482,7 @@
         contactInfo.formattedNumber = formattedNumber;
         contactInfo.normalizedNumber = number;
         contactInfo.photoId = 0;
-        mAdapter.injectContactInfoForTest(number, contactInfo);
+        mAdapter.injectContactInfoForTest(number, TEST_COUNTRY_ISO, contactInfo);
     }
 
     /**