Fix viewing legacy and raw contact URI on tablet

Bug: 5220640
Change-Id: I85c748389921bdff2639fff5f7713e00ba7f9f8c
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index c9fbeae..c711b6c 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -19,6 +19,7 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountTypeWithDataSet;
+import com.android.contacts.util.ContactLoaderUtils;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
@@ -681,7 +682,8 @@
         protected Result doInBackground(Void... args) {
             try {
                 final ContentResolver resolver = getContext().getContentResolver();
-                final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
+                final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
+                        resolver, mLookupUri);
                 Result result = loadContactEntity(resolver, uriCurrentFormat);
                 if (!result.isNotFound()) {
                     if (result.isDirectoryEntry()) {
@@ -706,47 +708,6 @@
             }
         }
 
-        /**
-         * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
-         * For legacy contacts, a raw-contact lookup is performed.
-         * @param resolver
-         */
-        private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
-            if (uri == null) throw new IllegalArgumentException("uri must not be null");
-
-            final String authority = uri.getAuthority();
-
-            // Current Style Uri?
-            if (ContactsContract.AUTHORITY.equals(authority)) {
-                final String type = resolver.getType(uri);
-                // Contact-Uri? Good, return it
-                if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    return uri;
-                }
-
-                // RawContact-Uri? Transform it to ContactUri
-                if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    final long rawContactId = ContentUris.parseId(uri);
-                    return RawContacts.getContactLookupUri(getContext().getContentResolver(),
-                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-                }
-
-                // Anything else? We don't know what this is
-                throw new IllegalArgumentException("uri format is unknown");
-            }
-
-            // Legacy Style? Convert to RawContact
-            final String OBSOLETE_AUTHORITY = "contacts";
-            if (OBSOLETE_AUTHORITY.equals(authority)) {
-                // Legacy Format. Convert to RawContact-Uri and then lookup the contact
-                final long rawContactId = ContentUris.parseId(uri);
-                return RawContacts.getContactLookupUri(resolver,
-                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-            }
-
-            throw new IllegalArgumentException("uri authority is unknown");
-        }
-
         private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
             Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
             Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 2d5fed1..2bde608 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -17,10 +17,10 @@
 
 import com.android.common.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.R;
+import com.android.contacts.util.ContactLoaderUtils;
 import com.android.contacts.widget.AutoScrollListView;
 
 import android.app.Activity;
-import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Loader;
@@ -28,6 +28,7 @@
 import android.content.SharedPreferences.Editor;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -92,51 +93,64 @@
     private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX;
 
     protected OnContactBrowserActionListener mListener;
+    private ContactLookupTask mContactLookupTask;
 
-    /**
-     * Refreshes a contact URI: it may have changed as a result of aggregation
-     * activity.
-     */
-    private class ContactUriQueryHandler extends AsyncQueryHandler {
+    private final class ContactLookupTask extends AsyncTask<Void, Void, Uri> {
 
-        public ContactUriQueryHandler(ContentResolver cr) {
-            super(cr);
-        }
+        private final Uri mUri;
+        private boolean mIsCancelled;
 
-        public void runQuery() {
-            startQuery(0, mSelectedContactUri, mSelectedContactUri,
-                    new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null);
+        public ContactLookupTask(Uri uri) {
+            mUri = uri;
         }
 
         @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor data) {
-            long contactId = 0;
-            String lookupKey = null;
-            if (data != null) {
-                if (data.moveToFirst()) {
-                    contactId = data.getLong(0);
-                    lookupKey = data.getString(1);
-                }
-                data.close();
-            }
+        protected Uri doInBackground(Void... args) {
+            Cursor cursor = null;
+            try {
+                final ContentResolver resolver = getContext().getContentResolver();
+                final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mUri);
+                cursor = resolver.query(uriCurrentFormat,
+                        new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null);
 
-            if (!cookie.equals(mSelectedContactUri)) {
+                if (cursor != null && cursor.moveToFirst()) {
+                    final long contactId = cursor.getLong(0);
+                    final String lookupKey = cursor.getString(1);
+                    if (contactId != 0 && !TextUtils.isEmpty(lookupKey)) {
+                        return Contacts.getLookupUri(contactId, lookupKey);
+                    }
+                }
+
+                Log.e(TAG, "Error: No contact ID or lookup key for contact " + mUri);
+                return null;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
+        public void cancel() {
+            super.cancel(true);
+            // Use a flag to keep track of whether the {@link AsyncTask} was cancelled or not in
+            // order to ensure onPostExecute() is not executed after the cancel request. The flag is
+            // necessary because {@link AsyncTask} still calls onPostExecute() if the cancel request
+            // came after the worker thread was finished.
+            mIsCancelled = true;
+        }
+
+        @Override
+        protected void onPostExecute(Uri uri) {
+            // Make sure the {@link Fragment} is at least still attached to the {@link Activity}
+            // before continuing.
+            if (mIsCancelled || !isAdded() || uri == null) {
                 return;
             }
-
-            Uri uri;
-            if (contactId != 0 && lookupKey != null) {
-                uri = Contacts.getLookupUri(contactId, lookupKey);
-            } else {
-                uri = null;
-            }
-
-            onContactUriQueryFinished(uri);
+            mSelectedContactUri = uri;
+            onContactUriQueryFinished(mSelectedContactUri);
         }
     }
 
-    private ContactUriQueryHandler mQueryHandler;
-
     private boolean mDelaySelection;
 
     private Handler getHandler() {
@@ -158,7 +172,6 @@
     @Override
     public void onAttach(Activity activity) {
         super.onAttach(activity);
-        mQueryHandler = new ContactUriQueryHandler(activity.getContentResolver());
         mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
         restoreFilter();
         restoreSelectedUri(false);
@@ -228,12 +241,10 @@
     }
 
     protected void refreshSelectedContactUri() {
-        if (mQueryHandler == null) {
-            return;
+        if (mContactLookupTask != null) {
+            mContactLookupTask.cancel();
         }
 
-        mQueryHandler.cancelOperation(0);
-
         if (!isSelectionVisible()) {
             return;
         }
@@ -249,7 +260,8 @@
                 && mSelectedContactDirectoryId != Directory.LOCAL_INVISIBLE) {
             onContactUriQueryFinished(mSelectedContactUri);
         } else {
-            mQueryHandler.runQuery();
+            mContactLookupTask = new ContactLookupTask(mSelectedContactUri);
+            mContactLookupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
         }
     }
 
diff --git a/src/com/android/contacts/util/ContactLoaderUtils.java b/src/com/android/contacts/util/ContactLoaderUtils.java
new file mode 100644
index 0000000..91c683f
--- /dev/null
+++ b/src/com/android/contacts/util/ContactLoaderUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 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.util;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+
+/**
+ * Utility methods for the {@link ContactLoader}.
+ */
+public final class ContactLoaderUtils {
+
+    /** Static helper, not instantiable. */
+    private ContactLoaderUtils() {}
+
+    /**
+     * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
+     * For legacy contacts, a raw-contact lookup is performed. An {@link IllegalArgumentException}
+     * can be thrown if the URI is null or the authority is not recognized.
+     *
+     * Do not call from the UI thread.
+     */
+    @SuppressWarnings("deprecation")
+    public static Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri)
+            throws IllegalArgumentException {
+        if (uri == null) throw new IllegalArgumentException("uri must not be null");
+
+        final String authority = uri.getAuthority();
+
+        // Current Style Uri?
+        if (ContactsContract.AUTHORITY.equals(authority)) {
+            final String type = resolver.getType(uri);
+            // Contact-Uri? Good, return it
+            if (ContactsContract.Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+                return uri;
+            }
+
+            // RawContact-Uri? Transform it to ContactUri
+            if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+                final long rawContactId = ContentUris.parseId(uri);
+                return RawContacts.getContactLookupUri(resolver,
+                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+            }
+
+            // Anything else? We don't know what this is
+            throw new IllegalArgumentException("uri format is unknown");
+        }
+
+        // Legacy Style? Convert to RawContact
+        final String OBSOLETE_AUTHORITY = Contacts.AUTHORITY;
+        if (OBSOLETE_AUTHORITY.equals(authority)) {
+            // Legacy Format. Convert to RawContact-Uri and then lookup the contact
+            final long rawContactId = ContentUris.parseId(uri);
+            return RawContacts.getContactLookupUri(resolver,
+                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+        }
+
+        throw new IllegalArgumentException("uri authority is unknown");
+    }
+}