Manualy sync Contacts to match ContactsCommon (1/2)

Bug 30759296

Change-Id: I3f1d1960c7a44ebcd9b8e12db5f8da7989e367ab
diff --git a/res-common/layout/contact_list_content.xml b/res-common/layout/contact_list_content.xml
index f18267d..8420b26 100644
--- a/res-common/layout/contact_list_content.xml
+++ b/res-common/layout/contact_list_content.xml
@@ -39,20 +39,29 @@
         android:id="@+id/contact_list">
 
         <include layout="@layout/contact_list_card"/>
-        <view
-            class="com.android.contacts.common.list.PinnedHeaderListView"
-            android:id="@android:id/list"
+
+        <android.support.v4.widget.SwipeRefreshLayout
+            android:id="@+id/swipe_refresh"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_marginLeft="?attr/contact_browser_list_padding_left"
-            android:layout_marginRight="?attr/contact_browser_list_padding_right"
-            android:layout_marginStart="?attr/contact_browser_list_padding_left"
-            android:layout_marginEnd="?attr/contact_browser_list_padding_right"
-            android:paddingTop="?attr/list_item_padding_top"
-            android:clipToPadding="false"
-            android:fastScrollEnabled="true"
-            android:visibility="gone"
-            android:fadingEdge="none" />
+            android:enabled="false" >
+
+            <view
+                class="com.android.contacts.common.list.PinnedHeaderListView"
+                android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="?attr/contact_browser_list_padding_left"
+                android:layout_marginRight="?attr/contact_browser_list_padding_right"
+                android:layout_marginStart="?attr/contact_browser_list_padding_left"
+                android:layout_marginEnd="?attr/contact_browser_list_padding_right"
+                android:paddingTop="?attr/list_item_padding_top"
+                android:clipToPadding="false"
+                android:fastScrollEnabled="true"
+		android:visibility="gone"
+                android:fadingEdge="none" />
+        </android.support.v4.widget.SwipeRefreshLayout>
+
         <ProgressBar
             android:id="@+id/search_progress"
             style="?android:attr/progressBarStyleLarge"
diff --git a/src/com/android/contacts/common/Experiments.java b/src/com/android/contacts/common/Experiments.java
index c811e27..8a03fdc 100644
--- a/src/com/android/contacts/common/Experiments.java
+++ b/src/com/android/contacts/common/Experiments.java
@@ -20,6 +20,16 @@
  */
 public final class Experiments {
 
+    /**
+     * Flag to control pullToRefresh feature.
+     */
+    public static final String PULL_TO_REFRESH = "pull_to_refresh";
+
+    /**
+     * Search study boolean indicating whether to inject yenta search results before CP2 results.
+     */
+    public static final String SEARCH_YENTA = "Search__yenta";
+
     private Experiments() {
     }
 }
diff --git a/src/com/android/contacts/common/list/ContactEntryListFragment.java b/src/com/android/contacts/common/list/ContactEntryListFragment.java
index 0c72d68..387b303 100644
--- a/src/com/android/contacts/common/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/common/list/ContactEntryListFragment.java
@@ -119,7 +119,7 @@
     private boolean mEnabled = true;
 
     private T mAdapter;
-    private View mView;
+    protected View mView;
     private ListView mListView;
 
     /**
diff --git a/src/com/android/contacts/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java
index 6d60a82..3770cc8 100644
--- a/src/com/android/contacts/common/list/ContactListFilter.java
+++ b/src/com/android/contacts/common/list/ContactListFilter.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.common.list;
 
+import android.accounts.Account;
 import android.content.SharedPreferences;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -25,6 +26,11 @@
 import android.text.TextUtils;
 
 import com.android.contacts.common.logging.ListEvent;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.account.GoogleAccountType;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Contact list filter parameters.
@@ -364,4 +370,59 @@
                 return "(unknown)";
         }
     }
+
+    /**
+     * Returns true if this ContactListFilter contains at least one Google account.
+     * (see {@link #isGoogleAccountType)
+     */
+    public boolean isSyncable(List<AccountWithDataSet> accounts) {
+        // TODO(samchen): Check FILTER_TYPE_CUSTOM
+        if (isGoogleAccountType() && filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+            return true;
+        }
+        if (filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+                || filterType == ContactListFilter.FILTER_TYPE_DEFAULT) {
+            if (accounts != null && accounts.size() > 0) {
+                // If we're showing all contacts and there is any Google account on the device then
+                // we're syncable.
+                for (AccountWithDataSet account : accounts) {
+                    if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type)
+                            && account.dataSet == null) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the Google accounts (see {@link #isGoogleAccountType) for this ContactListFilter.
+     */
+    public List<Account> getSyncableAccounts(List<AccountWithDataSet> accounts) {
+        final List<Account> syncableAccounts = new ArrayList<>();
+        // TODO(samchen): Check FILTER_TYPE_CUSTOM
+        if (isGoogleAccountType() && filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+            syncableAccounts.add(new Account(accountName, accountType));
+        } else if (filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+                || filterType == ContactListFilter.FILTER_TYPE_DEFAULT) {
+            if (accounts != null && accounts.size() > 0) {
+                for (AccountWithDataSet account : accounts) {
+                    if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type)
+                            && account.dataSet == null) {
+                        syncableAccounts.add(new Account(account.name, account.type));
+                    }
+                }
+            }
+        }
+        return syncableAccounts;
+    }
+
+    /**
+     * Returns true if this ContactListFilter is Google account type. (i.e. where
+     * accountType = "com.google" and dataSet = null)
+     */
+    public boolean isGoogleAccountType() {
+        return GoogleAccountType.ACCOUNT_TYPE.equals(accountType) && dataSet == null;
+    }
 }
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 3ef3502..0ed99d6 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -501,7 +501,7 @@
             boolean syncable =
                 ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) > 0;
 
-            if (syncable) {
+            if (syncable || GoogleAccountType.ACCOUNT_TYPE.equals(account.type)) {
                 List<AccountType> accountTypes = accountTypesByType.get(account.type);
                 if (accountTypes != null) {
                     // Add an account-with-data-set entry for each account type that is
diff --git a/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java b/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java
index 0634df4..6093405 100644
--- a/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java
@@ -17,6 +17,8 @@
 package com.android.contacts.common.vcard;
 
 import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,9 +29,11 @@
 import android.nfc.NfcAdapter;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.provider.ContactsContract.RawContacts;
 import android.util.Log;
+import android.widget.Toast;
 
 import com.android.contacts.common.R;
 import com.android.contacts.common.activity.RequestPermissionsActivity;
@@ -59,6 +63,12 @@
 
     private NdefRecord mRecord;
     private AccountWithDataSet mAccount;
+    private Handler mHandler = new Handler();
+
+    /**
+     * Notification id used when error happened before sending an import request to VCardServer.
+     */
+    private static final int FAILURE_NOTIFICATION_ID = 1;
 
     /* package */ class ImportTask extends AsyncTask<VCardService, Void, ImportRequest> {
         @Override
@@ -81,6 +91,10 @@
 
         @Override
         public void onPostExecute(ImportRequest request) {
+            if (request == null) {
+                // Finish the activity in case of error so it doesn't stay in view.
+                finish();
+            }
             unbindService(NfcImportVCardActivity.this);
         }
     }
@@ -111,6 +125,8 @@
                     parser.addInterpreter(detector);
                     parser.parse(is);
                 } catch (VCardVersionException e2) {
+                    Log.e(TAG, "vCard with unsupported version.");
+                    showFailureNotification(R.string.fail_reason_not_supported);
                     return null;
                 }
             } finally {
@@ -120,14 +136,16 @@
                 }
             }
         } catch (IOException e) {
-            Log.e(TAG, "Failed reading vcard data", e);
+            Log.e(TAG, "Failed reading vCard data", e);
+            showFailureNotification(R.string.fail_reason_io_error);
             return null;
         } catch (VCardNestedException e) {
             Log.w(TAG, "Nested Exception is found (it may be false-positive).");
             // Go through without throwing the Exception, as we may be able to detect the
             // version before it
         } catch (VCardException e) {
-            Log.e(TAG, "Error parsing vcard", e);
+            Log.e(TAG, "Error parsing vCard", e);
+            showFailureNotification(R.string.fail_reason_not_supported);
             return null;
         }
 
@@ -242,7 +260,8 @@
             Log.i(TAG, "Late import failure -- ignoring");
             return;
         }
-        // TODO: report failure
+        showFailureNotification(R.string.vcard_import_request_rejected_message);
+        finish();
     }
 
     @Override
@@ -269,4 +288,22 @@
     public void onComplete() {
         // do nothing
     }
+
+    /* package */ void showFailureNotification(int reasonId) {
+        final NotificationManager notificationManager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        final Notification notification =
+                NotificationImportExportListener.constructImportFailureNotification(
+                        this,
+                        getString(reasonId));
+        notificationManager.notify(NotificationImportExportListener.FAILURE_NOTIFICATION_TAG,
+                FAILURE_NOTIFICATION_ID, notification);
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                Toast.makeText(NfcImportVCardActivity.this,
+                        getString(R.string.vcard_import_failed), Toast.LENGTH_LONG).show();
+            }
+        });
+    }
 }