Merge "Add all intents accpeted by PeopleActivity to AllIntentsActivity" into ub-contactsdialer-f-dev
am: 9a580bd9f1

Change-Id: Ie528385aeb0a75290dad4e0f827fd926b523548d
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1987fb2..3fb0800 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,6 +49,8 @@
     <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" />
     <!-- Following used for Contact metadata syncing -->
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+    <!-- Following used for getting the status of the contacts sync adapter -->
+    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
 
     <uses-feature android:name="android.hardware.telephony" android:required="false"/>
 
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 3078976..a4981a8 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -52,7 +52,7 @@
     <string name="menu_call" msgid="3992595586042260618">"Pozovi kontakt"</string>
     <string name="menu_sendSMS" msgid="5535886767547006515">"Pošalji SMS kontaktu"</string>
     <string name="menu_splitAggregate" msgid="2627252205317945563">"Razdvoji"</string>
-    <string name="menu_editGroup" msgid="5062005185370983720">"Izmeni"</string>
+    <string name="menu_editGroup" msgid="6696843438454341063">"Ukloni kontakte"</string>
     <string name="menu_renameGroup" msgid="7169512355179757182">"Preimenuj oznaku"</string>
     <string name="menu_deleteGroup" msgid="1126469629233412249">"Izbriši oznaku"</string>
     <string name="menu_addToGroup" msgid="3267409983764370041">"Dodaj kontakt"</string>
@@ -80,10 +80,15 @@
       <item quantity="few">Kontakti su izbrisani</item>
       <item quantity="other">Kontakti su izbrisani</item>
     </plurals>
-    <plurals name="contacts_count" formatted="false" msgid="3287407967505649458">
-      <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> kontakt</item>
-      <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> kontakta</item>
-      <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> kontakata</item>
+    <plurals name="contacts_count" formatted="false" msgid="8696793457340503668">
+      <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontakt</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakta</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakata</item>
+    </plurals>
+    <plurals name="contacts_count_with_account" formatted="false" msgid="7402583111980220575">
+      <item quantity="one"><xliff:g id="COUNT_2">%d</xliff:g> kontakt · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="few"><xliff:g id="COUNT_2">%d</xliff:g> kontakta · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="other"><xliff:g id="COUNT_2">%d</xliff:g> kontakata · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
     </plurals>
     <string name="title_from_google" msgid="4664084747121207202">"Sa Google-a"</string>
     <string name="title_from_other_accounts" msgid="8307885412426754288">"Sa <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
@@ -237,19 +242,18 @@
     <string name="cancel_confirmation_dialog_keep_editing_button" msgid="7737724111972855348">"Nastavi izmene"</string>
     <string name="call_type_and_date" msgid="747163730039311423">"<xliff:g id="CALL_TYPE">%1$s</xliff:g> <xliff:g id="CALL_SHORT_DATE">%2$s</xliff:g>"</string>
     <string name="enter_contact_name" msgid="4594274696120278368">"Pretražite kontakte"</string>
-    <!-- no translation found for title_edit_group (1889302367574226969) -->
-    <skip />
+    <string name="title_edit_group" msgid="8602752287270586734">"Uklonite kontakte"</string>
     <string name="local_profile_title" msgid="2021416826991393684">"Moj lokalni profil"</string>
     <string name="external_profile_title" msgid="8034998767621359438">"Moj <xliff:g id="EXTERNAL_SOURCE">%1$s</xliff:g> profil"</string>
     <string name="toast_displaying_all_contacts" msgid="2737388783898593875">"Prikazani su svi kontakti"</string>
     <string name="generic_no_account_prompt" msgid="7218827704367325460">"Očuvajte bezbednost kontakata čak i ako izgubite telefon – sinhronizujte sa uslugom na mreži."</string>
     <string name="generic_no_account_prompt_title" msgid="753783911899054860">"Dodavanje naloga"</string>
-    <string name="contact_editor_prompt_zero_accounts" msgid="1785345895691886499">"Neće biti napravljena rezervna kopija novog kontakta. Želite li da dodate nalog koji pravi rezervnu kopiju kontakata onlajn?"</string>
+    <!-- no translation found for contact_editor_prompt_zero_accounts (6648376557574360096) -->
+    <skip />
     <string name="contact_editor_prompt_one_account" msgid="3087691056345099310">"Novi kontakti će biti sačuvani na nalogu <xliff:g id="ACCOUNT_NAME">%1$s</xliff:g>."</string>
     <string name="contact_editor_prompt_multiple_accounts" msgid="8565761674283473549">"Izaberite podrazumevani nalog za nove kontakte:"</string>
     <string name="contact_editor_title_new_contact" msgid="7192223018128934940">"Dodaj novi kontakt"</string>
     <string name="contact_editor_title_existing_contact" msgid="4898475703683187798">"Izmeni kontakt"</string>
-    <string name="keep_local" msgid="1258761699192993322">"Zadrži lokalno"</string>
     <string name="add_account" msgid="8201790677994503186">"Dodaj nalog"</string>
     <string name="add_new_account" msgid="5748627740680940264">"Dodaj novi nalog"</string>
     <string name="menu_export_database" msgid="2659719297530170820">"Izvezi datoteke baze podataka"</string>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index 2eee399..3b3d1a7 100644
--- a/res/values-be-rBY/strings.xml
+++ b/res/values-be-rBY/strings.xml
@@ -52,7 +52,7 @@
     <string name="menu_call" msgid="3992595586042260618">"Выклікаць кантакт"</string>
     <string name="menu_sendSMS" msgid="5535886767547006515">"Паведамленне кантакту"</string>
     <string name="menu_splitAggregate" msgid="2627252205317945563">"Выдаліць сувязь"</string>
-    <string name="menu_editGroup" msgid="5062005185370983720">"Рэдагаваць"</string>
+    <string name="menu_editGroup" msgid="6696843438454341063">"Выдаліць кантакты"</string>
     <string name="menu_renameGroup" msgid="7169512355179757182">"Перайменаваць метку"</string>
     <string name="menu_deleteGroup" msgid="1126469629233412249">"Выдаліць метку"</string>
     <string name="menu_addToGroup" msgid="3267409983764370041">"Дадаць кантакт"</string>
@@ -81,11 +81,17 @@
       <item quantity="many">Кантакты выдалены</item>
       <item quantity="other">Кантакты выдалены</item>
     </plurals>
-    <plurals name="contacts_count" formatted="false" msgid="3287407967505649458">
-      <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> кантакт</item>
-      <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> кантакты</item>
-      <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> кантактаў</item>
-      <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> кантакту</item>
+    <plurals name="contacts_count" formatted="false" msgid="8696793457340503668">
+      <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> кантакт</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> кантакты</item>
+      <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> кантактаў</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> кантакту</item>
+    </plurals>
+    <plurals name="contacts_count_with_account" formatted="false" msgid="7402583111980220575">
+      <item quantity="one"><xliff:g id="COUNT_2">%d</xliff:g> кантакт · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="few"><xliff:g id="COUNT_2">%d</xliff:g> кантакты · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="many"><xliff:g id="COUNT_2">%d</xliff:g> кантактаў · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="other"><xliff:g id="COUNT_2">%d</xliff:g> кантакту · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
     </plurals>
     <string name="title_from_google" msgid="4664084747121207202">"З Google"</string>
     <string name="title_from_other_accounts" msgid="8307885412426754288">"З <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
@@ -243,19 +249,18 @@
     <string name="cancel_confirmation_dialog_keep_editing_button" msgid="7737724111972855348">"Працягнуць рэдагаванне"</string>
     <string name="call_type_and_date" msgid="747163730039311423">"<xliff:g id="CALL_TYPE">%1$s</xliff:g> <xliff:g id="CALL_SHORT_DATE">%2$s</xliff:g>"</string>
     <string name="enter_contact_name" msgid="4594274696120278368">"Пошук кантактаў"</string>
-    <!-- no translation found for title_edit_group (1889302367574226969) -->
-    <skip />
+    <string name="title_edit_group" msgid="8602752287270586734">"Выдаліць кантакты"</string>
     <string name="local_profile_title" msgid="2021416826991393684">"Мой лакальны профіль"</string>
     <string name="external_profile_title" msgid="8034998767621359438">"Мой профіль у <xliff:g id="EXTERNAL_SOURCE">%1$s</xliff:g>"</string>
     <string name="toast_displaying_all_contacts" msgid="2737388783898593875">"Адлюстраванне ўсіх кантактаў"</string>
     <string name="generic_no_account_prompt" msgid="7218827704367325460">"Абаранiце свае кантакты, нават калі страціце тэлефон: сінхранізуйце iх з вэб-службай."</string>
     <string name="generic_no_account_prompt_title" msgid="753783911899054860">"Дадаць уліковы запіс"</string>
-    <string name="contact_editor_prompt_zero_accounts" msgid="1785345895691886499">"Рэзервовая копiя вашага новага кантакту не будзе створана. Дадаць улiковы запiс, у якiм будуць захоўвацца рэзервовыя копii кантактаў у Iнтэрнэце?"</string>
+    <!-- no translation found for contact_editor_prompt_zero_accounts (6648376557574360096) -->
+    <skip />
     <string name="contact_editor_prompt_one_account" msgid="3087691056345099310">"Новыя кантакты будуць захаваны ва ўліковы запіс <xliff:g id="ACCOUNT_NAME">%1$s</xliff:g>."</string>
     <string name="contact_editor_prompt_multiple_accounts" msgid="8565761674283473549">"Выбраць уліковы запіс па змаўчанні для новых кантактаў:"</string>
     <string name="contact_editor_title_new_contact" msgid="7192223018128934940">"Дадаць новы кантакт"</string>
     <string name="contact_editor_title_existing_contact" msgid="4898475703683187798">"Рэдагаваць кантакт"</string>
-    <string name="keep_local" msgid="1258761699192993322">"Захоўваць лакальна"</string>
     <string name="add_account" msgid="8201790677994503186">"Дадаць уліковы запіс"</string>
     <string name="add_new_account" msgid="5748627740680940264">"Дадаць новы ўліковы запіс"</string>
     <string name="menu_export_database" msgid="2659719297530170820">"Экспарт базы дадзеных файлаў"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 67f5166..1a16ab2 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -52,7 +52,7 @@
     <string name="menu_call" msgid="3992595586042260618">"Pozovi kontakt"</string>
     <string name="menu_sendSMS" msgid="5535886767547006515">"Pošalji tekstualnu poruku kontaktu"</string>
     <string name="menu_splitAggregate" msgid="2627252205317945563">"Razdvoji"</string>
-    <string name="menu_editGroup" msgid="5062005185370983720">"Uredi"</string>
+    <string name="menu_editGroup" msgid="6696843438454341063">"Ukloni kontakte"</string>
     <string name="menu_renameGroup" msgid="7169512355179757182">"Preimenuj oznaku"</string>
     <string name="menu_deleteGroup" msgid="1126469629233412249">"Izbriši oznaku"</string>
     <string name="menu_addToGroup" msgid="3267409983764370041">"Dodaj kontakt"</string>
@@ -80,10 +80,15 @@
       <item quantity="few">Kontakti su izbrisani</item>
       <item quantity="other">Kontakti su izbrisani</item>
     </plurals>
-    <plurals name="contacts_count" formatted="false" msgid="3287407967505649458">
-      <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> kontakt</item>
-      <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> kontakta</item>
-      <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> kontakata</item>
+    <plurals name="contacts_count" formatted="false" msgid="8696793457340503668">
+      <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontakt</item>
+      <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakta</item>
+      <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakata</item>
+    </plurals>
+    <plurals name="contacts_count_with_account" formatted="false" msgid="7402583111980220575">
+      <item quantity="one"><xliff:g id="COUNT_2">%d</xliff:g> kontakt · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="few"><xliff:g id="COUNT_2">%d</xliff:g> kontakta · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
+      <item quantity="other"><xliff:g id="COUNT_2">%d</xliff:g> kontakata · <xliff:g id="ACCOUNT_3">%s</xliff:g></item>
     </plurals>
     <string name="title_from_google" msgid="4664084747121207202">"Sa Googlea"</string>
     <string name="title_from_other_accounts" msgid="8307885412426754288">"Sa računa <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
@@ -237,19 +242,18 @@
     <string name="cancel_confirmation_dialog_keep_editing_button" msgid="7737724111972855348">"Nastavi uređivanje"</string>
     <string name="call_type_and_date" msgid="747163730039311423">"<xliff:g id="CALL_TYPE">%1$s</xliff:g> <xliff:g id="CALL_SHORT_DATE">%2$s</xliff:g>"</string>
     <string name="enter_contact_name" msgid="4594274696120278368">"Traži kontakte"</string>
-    <!-- no translation found for title_edit_group (1889302367574226969) -->
-    <skip />
+    <string name="title_edit_group" msgid="8602752287270586734">"Ukloni kontakte"</string>
     <string name="local_profile_title" msgid="2021416826991393684">"Moj lokalni profil"</string>
     <string name="external_profile_title" msgid="8034998767621359438">"Moj profil <xliff:g id="EXTERNAL_SOURCE">%1$s</xliff:g>"</string>
     <string name="toast_displaying_all_contacts" msgid="2737388783898593875">"Prikazuju se svi kontakti"</string>
     <string name="generic_no_account_prompt" msgid="7218827704367325460">"Očuvajte sigurnost svojih kontakata čak i ako izgubite telefon: sinhronizirajte ih s mrežnom uslugom."</string>
     <string name="generic_no_account_prompt_title" msgid="753783911899054860">"Dodajte račun"</string>
-    <string name="contact_editor_prompt_zero_accounts" msgid="1785345895691886499">"Neće biti napravljena sigurnosna kopija novog računa. Dodati račun za pravljenje sigurnosnih kopija kontakata na mreži?"</string>
+    <!-- no translation found for contact_editor_prompt_zero_accounts (6648376557574360096) -->
+    <skip />
     <string name="contact_editor_prompt_one_account" msgid="3087691056345099310">"Novi kontakti će biti sačuvani na račun <xliff:g id="ACCOUNT_NAME">%1$s</xliff:g>."</string>
     <string name="contact_editor_prompt_multiple_accounts" msgid="8565761674283473549">"Izaberite zadani račun za nove kontakte:"</string>
     <string name="contact_editor_title_new_contact" msgid="7192223018128934940">"Dodaj novi kontakt"</string>
     <string name="contact_editor_title_existing_contact" msgid="4898475703683187798">"Uredi kontakt"</string>
-    <string name="keep_local" msgid="1258761699192993322">"Zadrži na uređaju"</string>
     <string name="add_account" msgid="8201790677994503186">"Dodajte račun"</string>
     <string name="add_new_account" msgid="5748627740680940264">"Dodaj novi račun"</string>
     <string name="menu_export_database" msgid="2659719297530170820">"Izvezi fajlove baze podataka"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f5bf4cc..3efa80f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -81,6 +81,12 @@
     <!-- Color of background of all empty states. -->
     <color name="empty_state_background">#efefef</color>
 
+    <!-- Colors of swipeRefreshLayout's spinning circle. -->
+    <color name="swipe_refresh_color1">#0f9d58</color>
+    <color name="swipe_refresh_color2">#dd4b37</color>
+    <color name="swipe_refresh_color3">#4285f4</color>
+    <color name="swipe_refresh_color4">#f4b400</color>
+
     <!-- Color of hamburger icon in promo -->
     <color name="hamburger_feature_highlight_inner_color">#00ffffff</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a73e449..36ada5a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -295,6 +295,9 @@
     <!-- Minimum height for group name EditText -->
     <dimen name="group_name_edit_text_min_height">48dp</dimen>
 
+    <!-- Distance to pull down before causing a refresh. -->
+    <dimen name="pull_to_refresh_distance">40dp</dimen>
+
     <!-- Elevation of contact list header -->
     <dimen name="contact_list_header_elevation">2dp</dimen>
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 2e32e11..84b6685 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -21,13 +21,16 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Intent;
+import android.content.SyncStatusObserver;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Parcelable;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Intents;
@@ -37,6 +40,7 @@
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
+import android.support.v4.widget.SwipeRefreshLayout;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyCharacterMap;
@@ -55,6 +59,7 @@
 import com.android.contacts.ContactsDrawerActivity;
 import com.android.contacts.R;
 import com.android.contacts.activities.ActionBarAdapter.TabState;
+import com.android.contacts.common.Experiments;
 import com.android.contacts.common.activity.RequestPermissionsActivity;
 import com.android.contacts.common.compat.CompatUtils;
 import com.android.contacts.common.interactions.ImportExportDialogFragment;
@@ -74,6 +79,7 @@
 import com.android.contacts.common.util.Constants;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.widget.FloatingActionButtonController;
+import com.android.contacts.commonbind.experiments.Flags;
 import com.android.contacts.editor.EditorIntents;
 import com.android.contacts.interactions.ContactDeletionInteraction;
 import com.android.contacts.interactions.ContactMultiDeletionInteraction;
@@ -88,6 +94,7 @@
 import com.android.contacts.quickcontact.QuickContactActivity;
 import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.SharedPreferenceUtil;
+import com.android.contacts.util.SyncUtil;
 import com.google.android.libraries.material.featurehighlight.FeatureHighlight;
 
 import java.util.List;
@@ -166,6 +173,54 @@
     private final int mInstanceId;
     private static final AtomicInteger sNextInstanceId = new AtomicInteger();
 
+    private Object mStatusChangeListenerHandle;
+
+    private final Handler mHandler = new Handler();
+
+    private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
+        public void onStatusChanged(int which) {
+            mHandler.post(new Runnable() {
+                public void run() {
+                    onSyncStateUpdated();
+                }
+            });
+        }
+    };
+
+    // Update sync status for accounts in current ContactListFilter
+    private void onSyncStateUpdated() {
+        if (mActionBarAdapter.isSearchMode() || mActionBarAdapter.isSelectionMode()) {
+            return;
+        }
+
+        final ContactListFilter filter = mContactListFilterController.getFilter();
+        if (filter != null) {
+            final SwipeRefreshLayout swipeRefreshLayout = mAllFragment.getSwipeRefreshLayout();
+            if (swipeRefreshLayout == null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
+                }
+                return;
+            }
+
+            final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this)
+                    .getAccounts(/* contactsWritableOnly */ true);
+            final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
+            // If one of the accounts is active or pending, use spinning circle to indicate one of
+            // the syncs is in progress.
+            if (syncableAccounts != null && syncableAccounts.size() > 0) {
+                for (Account account: syncableAccounts) {
+                    if (SyncUtil.isSyncStatusPendingOrActive(account)
+                            || SyncUtil.isUnsyncableGoogleAccount(account)) {
+                        swipeRefreshLayout.setRefreshing(true);
+                        return;
+                    }
+                }
+            }
+            swipeRefreshLayout.setRefreshing(false);
+        }
+    }
+
     public PeopleActivity() {
         mInstanceId = sNextInstanceId.getAndIncrement();
         mIntentResolver = new ContactsIntentResolver(this);
@@ -412,6 +467,11 @@
         mOptionsMenuContactsAvailable = false;
         mProviderStatusWatcher.stop();
         super.onPause();
+
+        if (Flags.getInstance(this).getBoolean(Experiments.PULL_TO_REFRESH)) {
+            ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
+            onSyncStateUpdated();
+        }
     }
 
     @Override
@@ -431,6 +491,15 @@
         // Current tab may have changed since the last onSaveInstanceState().  Make sure
         // the actual contents match the tab.
         updateFragmentsVisibility();
+
+        if (Flags.getInstance(this).getBoolean(Experiments.PULL_TO_REFRESH)) {
+            mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
+                    ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+                            | ContentResolver.SYNC_OBSERVER_TYPE_PENDING
+                            | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,
+                    mSyncStatusObserver);
+            onSyncStateUpdated();
+        }
         maybeShowHamburgerFeatureHighlight();
     }
 
@@ -583,6 +652,10 @@
                 maybeShowHamburgerFeatureHighlight();
                 invalidateOptionsMenu();
                 showFabWithAnimation(shouldShowFabForAccount());
+                // Determine whether the account has pullToRefresh feature
+                if (Flags.getInstance(this).getBoolean(Experiments.PULL_TO_REFRESH)) {
+                    setSwipeRefreshLayoutEnabledOrNot(mContactListFilterController.getFilter());
+                }
                 break;
             case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
                 final String queryString = mActionBarAdapter.getQueryString();
@@ -1461,6 +1534,33 @@
                         .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             }
         }
+
+        // Determine whether the account has pullToRefresh feature
+        if (Flags.getInstance(this).getBoolean(Experiments.PULL_TO_REFRESH)) {
+            setSwipeRefreshLayoutEnabledOrNot(filter);
+        }
+    }
+
+    private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) {
+        final SwipeRefreshLayout swipeRefreshLayout = mAllFragment.getSwipeRefreshLayout();
+        if (swipeRefreshLayout == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
+            }
+            return;
+        }
+
+        swipeRefreshLayout.setRefreshing(false);
+        swipeRefreshLayout.setEnabled(false);
+
+        if (filter != null && !mActionBarAdapter.isSearchMode()
+                && !mActionBarAdapter.isSelectionMode()) {
+            final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this)
+                    .getAccounts(/* contactsWritableOnly */ true);
+            if (filter.isSyncable(accounts)) {
+                swipeRefreshLayout.setEnabled(true);
+            }
+        }
     }
 
     private String getActionBarTitleForAccount(ContactListFilter filter) {
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 2060068..da33b55 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -92,7 +92,7 @@
     private boolean mSelectionVerified;
     private int mLastSelectedPosition = -1;
     private boolean mRefreshingContactUri;
-    private ContactListFilter mFilter;
+    protected ContactListFilter mFilter;
     private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX;
 
     protected OnContactBrowserActionListener mListener;
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 1bd09f8..90dbc69 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -15,11 +15,16 @@
  */
 package com.android.contacts.list;
 
+import android.accounts.Account;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.CursorLoader;
 import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.v4.widget.SwipeRefreshLayout;
 import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -34,12 +39,18 @@
 
 import com.android.contacts.R;
 import com.android.contacts.activities.PeopleActivity;
+import com.android.contacts.common.Experiments;
 import com.android.contacts.common.list.ContactListAdapter;
 import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.DefaultContactListAdapter;
 import com.android.contacts.common.list.FavoritesAndContactsLoader;
+import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.commonbind.experiments.Flags;
+import com.android.contacts.util.SyncUtil;
+
+import java.util.List;
 
 /**
  * Fragment containing a contact list used for browsing (as compared to
@@ -52,6 +63,7 @@
     private View mEmptyHomeView;
     private View mAccountFilterContainer;
     private TextView mSearchProgressText;
+    private SwipeRefreshLayout mSwipeRefreshLayout;
 
     public DefaultContactBrowseListFragment() {
         setPhotoLoaderEnabled(true);
@@ -76,7 +88,9 @@
 
     private void bindListHeader(int numberOfContacts) {
         final ContactListFilter filter = getFilter();
-        if (!isSearchMode() && numberOfContacts <= 0) {
+        // If the phone has at least one Google account whose sync status is unsyncable or pending
+        // or active, we have to make mAccountFilterContainer visible.
+        if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) {
             if (filter != null && filter.isContactsFilterType()) {
                 makeViewVisible(mEmptyHomeView);
             } else {
@@ -99,6 +113,38 @@
         }
     }
 
+    /**
+     * If at least one Google account is unsyncable or its sync status is pending or active, we
+     * should not show empty view even if the number of contacts is 0. We should show sync status
+     * with empty list instead.
+     */
+    private boolean shouldShowEmptyView(ContactListFilter filter) {
+        if (filter == null) {
+            return true;
+        }
+        // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM
+        if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType
+                || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) {
+            final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(getContext())
+                    .getAccounts(/* contactsWritableOnly */ true);
+            final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
+
+            if (syncableAccounts != null && syncableAccounts.size() > 0) {
+                for (Account account : syncableAccounts) {
+                    if (SyncUtil.isSyncStatusPendingOrActive(account)
+                            || SyncUtil.isUnsyncableGoogleAccount(account)) {
+                        return false;
+                    }
+                }
+            }
+        } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) {
+            final Account account = new Account(filter.accountName, filter.accountType);
+            return !(SyncUtil.isSyncStatusPendingOrActive(account)
+                    || SyncUtil.isUnsyncableGoogleAccount(account));
+        }
+        return true;
+    }
+
     // Show the view that's specified by id and hide the other two.
     private void makeViewVisible(View view) {
         mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE);
@@ -200,6 +246,10 @@
     protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
         super.onCreateView(inflater, container);
 
+        if (Flags.getInstance(getActivity()).getBoolean(Experiments.PULL_TO_REFRESH)) {
+            initSwipeRefreshLayout();
+
+        }
         // Putting the header view inside a container will allow us to make
         // it invisible later. See checkHeaderViewVisibility()
         FrameLayout headerContainer = new FrameLayout(inflater.getContext());
@@ -212,6 +262,55 @@
         mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);
     }
 
+    private void initSwipeRefreshLayout() {
+        mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
+        if (mSwipeRefreshLayout == null) {
+            return;
+        }
+
+        mSwipeRefreshLayout.setEnabled(true);
+        // Request sync contacts
+        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+            @Override
+            public void onRefresh() {
+                syncContacts(mFilter);
+            }
+        });
+        mSwipeRefreshLayout.setColorSchemeResources(
+                R.color.swipe_refresh_color1,
+                R.color.swipe_refresh_color2,
+                R.color.swipe_refresh_color3,
+                R.color.swipe_refresh_color4);
+        mSwipeRefreshLayout.setDistanceToTriggerSync(
+                (int) getResources().getDimension(R.dimen.pull_to_refresh_distance));
+    }
+
+    /**
+     * Request sync for the Google accounts (not include Google+ accounts) specified by the given
+     * filter.
+     */
+    private void syncContacts(ContactListFilter filter) {
+        if (filter == null) {
+            return;
+        }
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+
+        final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
+                getActivity()).getAccounts(/* contactsWritableOnly */ true);
+        final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
+        if (syncableAccounts != null && syncableAccounts.size() > 0) {
+            for (Account account : syncableAccounts) {
+                 // We can prioritize Contacts sync if sync is not initialized yet.
+                if (!SyncUtil.isSyncStatusPendingOrActive(account)
+                        || SyncUtil.isUnsyncableGoogleAccount(account)) {
+                    ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
+                }
+            }
+        }
+    }
+
     @Override
     protected void setSearchMode(boolean flag) {
         super.setSearchMode(flag);
@@ -260,4 +359,8 @@
             }
         }
     }
+
+    public SwipeRefreshLayout getSwipeRefreshLayout() {
+        return mSwipeRefreshLayout;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/util/SyncUtil.java b/src/com/android/contacts/util/SyncUtil.java
new file mode 100644
index 0000000..cef2223
--- /dev/null
+++ b/src/com/android/contacts/util/SyncUtil.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 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.accounts.Account;
+import android.content.ContentResolver;
+import android.provider.ContactsContract;
+
+import com.android.contacts.common.model.account.GoogleAccountType;
+
+import java.util.List;
+
+/**
+ * Utilities related to sync.
+ */
+public final class SyncUtil {
+    private static final String TAG = "SyncUtil";
+
+    private SyncUtil() {
+    }
+
+    public static final boolean isSyncStatusPendingOrActive(Account account) {
+        if (account == null) {
+            return false;
+        }
+        return ContentResolver.isSyncPending(account, ContactsContract.AUTHORITY)
+                || ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY);
+    }
+
+    /**
+     * Returns true if the given Google account is not syncable.
+     */
+    public static final boolean isUnsyncableGoogleAccount(Account account) {
+        if (account == null || !GoogleAccountType.ACCOUNT_TYPE.equals(account.type)) {
+            return false;
+        }
+        return ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) <= 0;
+    }
+}
diff --git a/tests/src/com/android/contacts/util/SyncUtilTests.java b/tests/src/com/android/contacts/util/SyncUtilTests.java
new file mode 100644
index 0000000..372a652
--- /dev/null
+++ b/tests/src/com/android/contacts/util/SyncUtilTests.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.accounts.Account;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for SyncUtil.
+ */
+@SmallTest
+public class SyncUtilTests extends AndroidTestCase {
+    private static final String TAG = "SyncUtilTests";
+
+    private static final String GOOGLE_TYPE = "com.google";
+    private static final String NOT_GOOGLE_TYPE = "com.abc";
+    private static final String ACCOUNT_NAME = "ACCOUNT_NAME";
+
+    private final Account mGoogleAccount;
+    private final Account mOtherAccount;
+
+    public SyncUtilTests() {
+        mGoogleAccount = new Account(ACCOUNT_NAME, GOOGLE_TYPE);
+        mOtherAccount = new Account(ACCOUNT_NAME, NOT_GOOGLE_TYPE);
+    }
+
+    public void testIsUnsyncableGoogleAccount() throws Exception {
+        // The account names of mGoogleAccount and mOtherAccount are not valid, so both accounts
+        // are not syncable.
+        assertTrue(SyncUtil.isUnsyncableGoogleAccount(mGoogleAccount));
+        assertFalse(SyncUtil.isUnsyncableGoogleAccount(mOtherAccount));
+        assertFalse(SyncUtil.isUnsyncableGoogleAccount(null));
+    }
+}