Add FeatureHighlight library to proguard.
am: 4a2abe9db2
Change-Id: Icfca14dca3a1eb8bf3b194fd208e4cbbf043f3b8
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 230fe9d..ea61e7b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.contacts"
- android:versionCode="10511"
- android:versionName="1.5.11">
+ android:versionCode="20000"
+ android:versionName="2.0.0">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
@@ -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/proguard.flags b/proguard.flags
index baff4b6..7b9bc89 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -15,6 +15,7 @@
# For test:
-keep class com.android.contacts.commonbind.analytics.AnalyticsUtil { *;}
-keep class com.android.contacts.interactions.** { *;}
+-keep class com.android.contacts.util.SyncUtil { *;}
-keep class com.google.common.base.Objects { *;}
-keep class com.google.common.base.Preconditions { *;}
-keep class com.google.common.collect.Lists { *;}
diff --git a/res/layout/floating_action_button.xml b/res/layout/floating_action_button.xml
index 95c76ae..2dc8955 100644
--- a/res/layout/floating_action_button.xml
+++ b/res/layout/floating_action_button.xml
@@ -14,24 +14,26 @@
limitations under the License.
-->
-<!-- This expects to be included inside a RelativeLayout -->
+<!-- This expects to be included inside a RelativeLayout or a CoordinatorLayout -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/floating_action_button_container"
android:layout_width="@dimen/floating_action_button_width"
android:layout_height="@dimen/floating_action_button_height"
- android:layout_marginEnd="@dimen/floating_action_button_margin_right"
- android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
- android:background="@drawable/fab_blue"
+ android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
- android:layout_alignParentBottom="true">
+ android:layout_gravity="bottom|end"
+ android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
+ android:layout_marginEnd="@dimen/floating_action_button_margin_right"
+ android:background="@drawable/fab_blue"
+ android:elevation="@dimen/design_fab_elevation">
<ImageButton
android:id="@+id/floating_action_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/floating_action_button"
- android:tint="@color/floating_action_button_icon_color"
android:contentDescription="@string/action_menu_add_new_contact_button"
- android:src="@drawable/ic_add"/>
+ android:src="@drawable/ic_add"
+ android:tint="@color/floating_action_button_icon_color"/>
</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/group_name_dialog.xml b/res/layout/group_name_dialog.xml
deleted file mode 100644
index f19df86..0000000
--- a/res/layout/group_name_dialog.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="25dip"
- android:paddingRight="25dip"
- android:paddingStart="25dip"
- android:paddingEnd="25dip"
- android:paddingTop="25dip"
- android:paddingBottom="25dip">
- <EditText
- android:id="@+id/group_label"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/group_name_edit_text_min_height"/>
-</FrameLayout>
diff --git a/res/layout/group_name_edit_dialog.xml b/res/layout/group_name_edit_dialog.xml
index 9a8c69f..5df8c9d 100644
--- a/res/layout/group_name_edit_dialog.xml
+++ b/res/layout/group_name_edit_dialog.xml
@@ -14,24 +14,28 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.design.widget.TextInputLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/text_input_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:paddingBottom="24dp"
+ android:paddingEnd="24dp"
android:paddingStart="24dp"
- android:paddingEnd="24dp">
+ app:errorEnabled="true"
+ app:hintEnabled="false">
- <EditText android:id="@android:id/text1"
- xmlns:android="http://schemas.android.com/apk/res/android"
+ <android.support.design.widget.TextInputEditText
+ android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/group_name_edit_text_min_height"
android:layout_marginBottom="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="16dp"
android:hint="@string/group_name_dialog_hint"
android:inputType="text"
- android:singleLine="true"
- android:maxLength="@integer/group_name_max_length"/>
-</LinearLayout>
\ No newline at end of file
+ android:maxLength="@integer/group_name_max_length"
+ android:minHeight="@dimen/group_name_edit_text_min_height"
+ android:singleLine="true"/>
+</android.support.design.widget.TextInputLayout>
\ No newline at end of file
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 3078976..b3c57d3 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>
@@ -111,10 +116,15 @@
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Naziv prilagođene oznake"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Šalji pozive direktno u glasovnu poštu"</string>
<string name="removePhoto" msgid="4898105274130284565">"Ukloni fotografiju"</string>
- <string name="noContacts" msgid="4955659076981974652">"Nema kontakata"</string>
+ <!-- no translation found for noContacts (2228592924476426108) -->
+ <skip />
<string name="noGroups" msgid="4607906327968232225">"Nema oznaka."</string>
<string name="noAccounts" msgid="7768267764545265909">"Morate da imate nalog da biste mogli da pravite grupe."</string>
<string name="emptyGroup" msgid="5102411903247859575">"Nema kontakata sa ovom oznakom"</string>
+ <!-- no translation found for emptyAccount (6873962901497975964) -->
+ <skip />
+ <!-- no translation found for emptyMainList (2772242747899664460) -->
+ <skip />
<string name="contactSavedToast" msgid="9171862279493213075">"Kontakt je sačuvan"</string>
<string name="contactUnlinkedToast" msgid="7122823195786012553">"Kontakti su razdvojeni"</string>
<string name="contactSavedErrorToast" msgid="3207250533172944892">"Nije moguće sačuvati izmene kontakata."</string>
@@ -223,7 +233,8 @@
<string name="social_widget_loading" msgid="5327336597364074608">"Učitava se…"</string>
<string name="contacts_unavailable_create_contact" msgid="7014525713871959208">"Napravi novi kontakt"</string>
<string name="contacts_unavailable_add_account" msgid="4347232421410561500">"Dodajte nalog"</string>
- <string name="contacts_unavailable_import_contacts" msgid="4957393255392437529">"Uvezi kontakte"</string>
+ <!-- no translation found for contacts_unavailable_import_contacts (3182801738595937144) -->
+ <skip />
<string name="create_group_dialog_title" msgid="5363404287877384473">"Napravite novu oznaku"</string>
<string name="create_group_item_label" msgid="3263064599743742865">"Napravi novu…"</string>
<string name="delete_group_dialog_message" msgid="335713829185261371">"Želite da izbrišete oznaku „<xliff:g id="GROUP_LABEL">%1$s</xliff:g>“? (Kontakti neće biti izbrisani.)"</string>
@@ -237,19 +248,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>
@@ -346,4 +356,8 @@
<string name="permission_explanation_subheader_calendar_and_SMS" msgid="630115334220569184">"Događaji i poruke"</string>
<string name="permission_explanation_subheader_calendar" msgid="8785323496211704613">"Događaji"</string>
<string name="permission_explanation_subheader_SMS" msgid="1904552086449525567">"Poruke"</string>
+ <!-- no translation found for hamburger_feature_highlight_header (7442308698936786415) -->
+ <skip />
+ <!-- no translation found for hamburger_feature_highlight_body (6268711111318172098) -->
+ <skip />
</resources>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index 2eee399..90530cb 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>
@@ -113,10 +119,15 @@
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Імя карыстальніцкага цэтліка"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Адпраўляць выклікі непасрэдна на галасавую пошту"</string>
<string name="removePhoto" msgid="4898105274130284565">"Выдаліць фота"</string>
- <string name="noContacts" msgid="4955659076981974652">"Няма кантактаў"</string>
+ <!-- no translation found for noContacts (2228592924476426108) -->
+ <skip />
<string name="noGroups" msgid="4607906327968232225">"Метак няма."</string>
<string name="noAccounts" msgid="7768267764545265909">"Для стварэння групы патрабуецца ўліковы запіс."</string>
<string name="emptyGroup" msgid="5102411903247859575">"Няма кантактаў з гэтай меткай"</string>
+ <!-- no translation found for emptyAccount (6873962901497975964) -->
+ <skip />
+ <!-- no translation found for emptyMainList (2772242747899664460) -->
+ <skip />
<string name="contactSavedToast" msgid="9171862279493213075">"Кантакт захаваны"</string>
<string name="contactUnlinkedToast" msgid="7122823195786012553">"Кантакты адлучаны"</string>
<string name="contactSavedErrorToast" msgid="3207250533172944892">"Немагчыма захаваць змены кантакта."</string>
@@ -229,7 +240,8 @@
<string name="social_widget_loading" msgid="5327336597364074608">"Загрузка..."</string>
<string name="contacts_unavailable_create_contact" msgid="7014525713871959208">"Стварыць новы кантакт"</string>
<string name="contacts_unavailable_add_account" msgid="4347232421410561500">"Дадаць уліковы запіс"</string>
- <string name="contacts_unavailable_import_contacts" msgid="4957393255392437529">"Імпартаваць кантакты"</string>
+ <!-- no translation found for contacts_unavailable_import_contacts (3182801738595937144) -->
+ <skip />
<string name="create_group_dialog_title" msgid="5363404287877384473">"Стварыць новую метку"</string>
<string name="create_group_item_label" msgid="3263064599743742865">"Стварыць новую..."</string>
<string name="delete_group_dialog_message" msgid="335713829185261371">"Выдаліць метку \"<xliff:g id="GROUP_LABEL">%1$s</xliff:g>\"? (Самі кантакты не будуць выдалены.)"</string>
@@ -243,19 +255,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>
@@ -356,4 +367,8 @@
<string name="permission_explanation_subheader_calendar_and_SMS" msgid="630115334220569184">"Падзеі і паведамленні"</string>
<string name="permission_explanation_subheader_calendar" msgid="8785323496211704613">"Падзеі"</string>
<string name="permission_explanation_subheader_SMS" msgid="1904552086449525567">"Паведамленні"</string>
+ <!-- no translation found for hamburger_feature_highlight_header (7442308698936786415) -->
+ <skip />
+ <!-- no translation found for hamburger_feature_highlight_body (6268711111318172098) -->
+ <skip />
</resources>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 67f5166..6a3ea47 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>
@@ -111,10 +116,15 @@
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Prilagođeni naziv oznake"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Pošalji pozive direktno na govornu poštu"</string>
<string name="removePhoto" msgid="4898105274130284565">"Ukloni fotografiju"</string>
- <string name="noContacts" msgid="4955659076981974652">"Nema kontakata"</string>
+ <!-- no translation found for noContacts (2228592924476426108) -->
+ <skip />
<string name="noGroups" msgid="4607906327968232225">"Nema oznaka."</string>
<string name="noAccounts" msgid="7768267764545265909">"Potreban vam je račun da napravite grupe."</string>
<string name="emptyGroup" msgid="5102411903247859575">"Nema kontakata s ovom oznakom"</string>
+ <!-- no translation found for emptyAccount (6873962901497975964) -->
+ <skip />
+ <!-- no translation found for emptyMainList (2772242747899664460) -->
+ <skip />
<string name="contactSavedToast" msgid="9171862279493213075">"Kontakt je sačuvan"</string>
<string name="contactUnlinkedToast" msgid="7122823195786012553">"Kontakti su razdvojeni"</string>
<string name="contactSavedErrorToast" msgid="3207250533172944892">"Nije moguće sačuvati promjene kontakta."</string>
@@ -223,7 +233,8 @@
<string name="social_widget_loading" msgid="5327336597364074608">"Učitavanje…"</string>
<string name="contacts_unavailable_create_contact" msgid="7014525713871959208">"Napravi novi kontakt"</string>
<string name="contacts_unavailable_add_account" msgid="4347232421410561500">"Dodaj račun"</string>
- <string name="contacts_unavailable_import_contacts" msgid="4957393255392437529">"Uvezi kontakte"</string>
+ <!-- no translation found for contacts_unavailable_import_contacts (3182801738595937144) -->
+ <skip />
<string name="create_group_dialog_title" msgid="5363404287877384473">"Napravi novu oznaku"</string>
<string name="create_group_item_label" msgid="3263064599743742865">"Napravi novu…"</string>
<string name="delete_group_dialog_message" msgid="335713829185261371">"Izbrisati oznaku \"<xliff:g id="GROUP_LABEL">%1$s</xliff:g>\"? (Kontakti neće biti izbrisani.)"</string>
@@ -237,19 +248,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>
@@ -346,4 +356,8 @@
<string name="permission_explanation_subheader_calendar_and_SMS" msgid="630115334220569184">"Događaji i poruke"</string>
<string name="permission_explanation_subheader_calendar" msgid="8785323496211704613">"Događaji"</string>
<string name="permission_explanation_subheader_SMS" msgid="1904552086449525567">"Poruke"</string>
+ <!-- no translation found for hamburger_feature_highlight_header (7442308698936786415) -->
+ <skip />
+ <!-- no translation found for hamburger_feature_highlight_body (6268711111318172098) -->
+ <skip />
</resources>
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/res/values/strings.xml b/res/values/strings.xml
index 7a81940..132064c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -369,6 +369,9 @@
<!-- Toast displayed when saving a label failed [CHAR LIMIT=70] -->
<string name="groupSavedErrorToast">Couldn\'t save label changes.</string>
+ <!-- Message displayed when creating a group with the same name as an existing group -->
+ <string name="groupExistsErrorMessage">That label already exists</string>
+
<!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" is selected -->
<plurals name="listTotalPhoneContacts">
<item quantity="one">1 contact with phone number</item>
@@ -632,9 +635,6 @@
Initiates a contact import dialog [CHAR LIMIT=128] -->
<string name="contacts_unavailable_import_contacts">Import</string>
- <!-- Title of the dialog that allows creation of a contact label [CHAR LIMIT=50] -->
- <string name="create_group_dialog_title">Create new label</string>
-
<!-- An item in the popup list of labels that triggers creation of a contact label [CHAR LIMIT=128] -->
<string name="create_group_item_label">Create new…</string>
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index e4eef51..bfdc2fb 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -88,8 +88,7 @@
AccountFiltersListener,
GroupsListener,
NavigationView.OnNavigationItemSelectedListener,
- SelectAccountDialogFragment.Listener,
- GroupNameEditDialogFragment.Listener {
+ SelectAccountDialogFragment.Listener {
protected static String TAG = "ContactsDrawerActivity";
@@ -650,26 +649,11 @@
@Override
public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
mNewGroupAccount = account;
- GroupNameEditDialogFragment.showInsertDialog(
- getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
+ GroupNameEditDialogFragment.newInstanceForCreation(mNewGroupAccount, ACTION_CREATE_GROUP)
+ .show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
}
@Override
public void onAccountSelectorCancelled() {
}
-
- @Override
- public void onGroupNameEdit(String groupName, boolean isInsert) {
- if (mNewGroupAccount == null) {
- Toast.makeText(this, R.string.groupCreateFailedToast, Toast.LENGTH_SHORT).show();
- return;
- }
- startService(ContactSaveService.createNewGroupIntent(this,
- mNewGroupAccount, groupName, /* rawContactsToAdd */ null, getClass(),
- ACTION_CREATE_GROUP));
- }
-
- @Override
- public void onGroupNameEditCancelled() {
- }
}
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
index 093c8b6..68f2f44 100644
--- a/src/com/android/contacts/activities/GroupMembersActivity.java
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -36,6 +36,7 @@
import com.android.contacts.common.logging.ListEvent;
import com.android.contacts.common.logging.Logger;
import com.android.contacts.common.logging.ScreenEvent.ScreenType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.group.GroupMembersFragment;
import com.android.contacts.group.GroupMetadata;
@@ -53,8 +54,7 @@
public class GroupMembersActivity extends ContactsDrawerActivity implements
ActionBarAdapter.Listener,
MultiSelectContactsListFragment.OnCheckBoxListActionListener,
- GroupMembersFragment.GroupMembersListener,
- GroupNameEditDialogFragment.Listener {
+ GroupMembersFragment.GroupMembersListener {
private static final String TAG = "GroupMembers";
@@ -382,8 +382,11 @@
return true;
}
case R.id.menu_rename_group: {
- GroupNameEditDialogFragment.showUpdateDialog(
- getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG, mGroupMetadata.groupName);
+ GroupNameEditDialogFragment.newInstanceForUpdate(
+ new AccountWithDataSet(mGroupMetadata.accountName,
+ mGroupMetadata.accountType, mGroupMetadata.dataSet),
+ ACTION_UPDATE_GROUP, mGroupMetadata.groupId, mGroupMetadata.groupName)
+ .show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
return true;
}
case R.id.menu_delete_group: {
@@ -567,23 +570,6 @@
mActionBarAdapter.setSelectionMode(false);
}
- // GroupNameEditDialogFragment.Listener callbacks
-
- @Override
- public void onGroupNameEdit(String groupName, boolean isInsert) {
- if (isInsert) {
- super.onGroupNameEdit(groupName, isInsert);
- return;
- }
- startService(ContactSaveService.createGroupRenameIntent(this,
- mGroupMetadata.groupId, groupName, GroupMembersActivity.class,
- ACTION_UPDATE_GROUP));
- }
-
- @Override
- public void onGroupNameEditCancelled() {
- }
-
// GroupMembersFragment callbacks
@Override
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/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index d41991c..ba0c2c6 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -38,9 +38,9 @@
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.dataitem.DataKind;
-import com.android.contacts.interactions.GroupCreationDialogFragment;
-import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
+import com.android.contacts.group.GroupNameEditDialogFragment;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.RawContactModifier;
@@ -56,6 +56,8 @@
public class GroupMembershipView extends LinearLayout
implements OnClickListener, OnItemClickListener {
+ public static final String TAG_CREATE_GROUP_FRAGMENT = "createGroupDialog";
+
private static final int CREATE_NEW_GROUP_GROUP_ID = 133;
public static final class GroupSelectionItem {
@@ -449,17 +451,23 @@
UiClosables.closeQuietly(mPopup);
mPopup = null;
- GroupCreationDialogFragment.show(
+ final GroupNameEditDialogFragment dialog =
+ GroupNameEditDialogFragment.newInstanceForCreation(
+ new AccountWithDataSet(mAccountName, mAccountType, mDataSet), null);
+
+ // If the device is rotated after the dialog is shown, the listener will become null,
+ // so that the popup from GroupMembershipView will not be shown.
+ dialog.setListener(new GroupNameEditDialogFragment.Listener() {
+ @Override
+ public void onGroupNameEditStarted(String groupName) {
+ mCreatedNewGroup = true;
+ }
+ @Override
+ public void onGroupNameEditCancelled() { }
+ });
+ dialog.show(
((Activity) getContext()).getFragmentManager(),
- mAccountType,
- mAccountName,
- mDataSet,
- new OnGroupCreatedListener() {
- @Override
- public void onGroupCreated() {
- mCreatedNewGroup = true;
- }
- });
+ TAG_CREATE_GROUP_FRAGMENT);
}
}
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
index 235a878..b2bfd0b 100644
--- a/src/com/android/contacts/group/GroupNameEditDialogFragment.java
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -17,11 +17,18 @@
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.FragmentManager;
+import android.app.LoaderManager;
import android.content.Context;
+import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextUtils;
@@ -33,60 +40,99 @@
import android.widget.EditText;
import android.widget.TextView;
+import com.android.contacts.ContactSaveService;
import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.google.common.base.Strings;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
* Edits the name of a group.
*/
-public final class GroupNameEditDialogFragment extends DialogFragment {
+public final class GroupNameEditDialogFragment extends DialogFragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
- private static final String KEY_IS_INSERT = "isInsert";
private static final String KEY_GROUP_NAME = "groupName";
private static final String ARG_IS_INSERT = "isInsert";
private static final String ARG_GROUP_NAME = "groupName";
+ private static final String ARG_ACCOUNT = "account";
+ private static final String ARG_CALLBACK_ACTION = "callbackAction";
+ private static final String ARG_GROUP_ID = "groupId";
+
+ private static final long NO_GROUP_ID = -1;
+
/** Callbacks for hosts of the {@link GroupNameEditDialogFragment}. */
public interface Listener {
- void onGroupNameEdit(String groupName, boolean isInsert);
+ void onGroupNameEditStarted(String name);
void onGroupNameEditCancelled();
+
+ public static final Listener None = new Listener() {
+ @Override
+ public void onGroupNameEditStarted(String name) { }
+
+ @Override
+ public void onGroupNameEditCancelled() { }
+ };
}
private boolean mIsInsert;
private String mGroupName;
+ private long mGroupId;
+ private Listener mListener;
+ private AccountWithDataSet mAccount;
private EditText mGroupNameEditText;
+ private TextInputLayout mGroupNameTextLayout;
+ private Set<String> mExistingGroups = Collections.emptySet();
- public static void showInsertDialog(FragmentManager fragmentManager, String tag) {
- showDialog(fragmentManager, tag, /* isInsert */ true, /* groupName */ null);
+ public static GroupNameEditDialogFragment newInstanceForCreation(
+ AccountWithDataSet account, String callbackAction) {
+ return newInstance(account, callbackAction, NO_GROUP_ID, null);
}
- public static void showUpdateDialog(FragmentManager fragmentManager,
- String tag, String groupName) {
- showDialog(fragmentManager, tag, /* isInsert */ false, groupName);
+ public static GroupNameEditDialogFragment newInstanceForUpdate(
+ AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
+ return newInstance(account, callbackAction, groupId, groupName);
}
- private static void showDialog(FragmentManager fragmentManager,
- String tag, boolean isInsert, String groupName) {
+ private static GroupNameEditDialogFragment newInstance(
+ AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
+ if (account == null || account.name == null || account.type == null) {
+ throw new IllegalArgumentException("Invalid account");
+ }
+ final boolean isInsert = groupId == NO_GROUP_ID;
final Bundle args = new Bundle();
args.putBoolean(ARG_IS_INSERT, isInsert);
+ args.putLong(ARG_GROUP_ID, groupId);
args.putString(ARG_GROUP_NAME, groupName);
+ args.putParcelable(ARG_ACCOUNT, account);
+ args.putString(ARG_CALLBACK_ACTION, callbackAction);
final GroupNameEditDialogFragment dialog = new GroupNameEditDialogFragment();
dialog.setArguments(args);
- dialog.show(fragmentManager, tag);
+ return dialog;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setStyle(STYLE_NORMAL, R.style.ContactsAlertDialogThemeAppCompat);
+ final Bundle args = getArguments();
if (savedInstanceState == null) {
- final Bundle args = getArguments();
- mIsInsert = args.getBoolean(KEY_IS_INSERT);
mGroupName = args.getString(KEY_GROUP_NAME);
} else {
- mIsInsert = savedInstanceState.getBoolean(ARG_IS_INSERT);
mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
}
+ mGroupId = args.getLong(ARG_GROUP_ID, NO_GROUP_ID);
+ mIsInsert = args.getBoolean(ARG_IS_INSERT, true);
+ mAccount = getArguments().getParcelable(ARG_ACCOUNT);
+
+ // There is only one loader so the id arg doesn't matter.
+ getLoaderManager().initLoader(0, null, this);
}
@Override
@@ -96,7 +142,7 @@
title.setText(mIsInsert
? R.string.group_name_dialog_insert_title
: R.string.group_name_dialog_update_title);
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), getTheme())
.setCustomTitle(title)
.setView(R.layout.group_name_edit_dialog)
.setNegativeButton(android.R.string.cancel, new OnClickListener() {
@@ -107,12 +153,9 @@
dismiss();
}
})
- .setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- getListener().onGroupNameEdit(getGroupName(), mIsInsert);
- }
- });
+ // The Positive button listener is defined below in the OnShowListener to
+ // allow for input validation
+ .setPositiveButton(android.R.string.ok, null);
// Disable the create button when the name is empty
final AlertDialog alertDialog = builder.create();
@@ -122,6 +165,8 @@
@Override
public void onShow(DialogInterface dialog) {
mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
+ mGroupNameTextLayout =
+ (TextInputLayout) alertDialog.findViewById(R.id.text_input_layout);
if (!TextUtils.isEmpty(mGroupName)) {
mGroupNameEditText.setText(mGroupName);
// Guard against already created group names that are longer than the max
@@ -134,6 +179,14 @@
final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));
+
+ // Override the click listener to prevent dismissal if creating a duplicate group.
+ createButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ maybePersistCurrentGroupName(v);
+ }
+ });
mGroupNameEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -145,6 +198,7 @@
@Override
public void afterTextChanged(Editable s) {
+ mGroupNameTextLayout.setError(null);
createButton.setEnabled(!TextUtils.isEmpty(s));
}
});
@@ -154,6 +208,56 @@
return alertDialog;
}
+ /**
+ * Sets the listener for the rename
+ *
+ * Setting a listener on a fragment is error prone since it will be lost if the fragment
+ * is recreated. This exists because it is used from a view class (GroupMembersView) which
+ * needs to modify it's state when this fragment updates the name.
+ *
+ * @param listener the listener. can be null
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ private boolean hasNameChanged() {
+ final String name = Strings.nullToEmpty(getGroupName());
+ final String originalName = getArguments().getString(ARG_GROUP_NAME);
+ return (mIsInsert && !name.isEmpty()) || !name.equals(originalName);
+ }
+
+ private void maybePersistCurrentGroupName(View button) {
+ if (!hasNameChanged()) {
+ dismiss();
+ return;
+ }
+ final String name = getGroupName();
+ // Note we don't check if the loader finished populating mExistingGroups. It's not the
+ // end of the world if the user ends up with a duplicate group and in practice it should
+ // never really happen (the query should complete much sooner than the user can edit the
+ // label)
+ if (mExistingGroups.contains(name)) {
+ mGroupNameTextLayout.setError(
+ getString(R.string.groupExistsErrorMessage));
+ button.setEnabled(false);
+ return;
+ }
+ final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
+ final Intent serviceIntent;
+ if (mIsInsert) {
+ serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(),
+ new AccountWithDataSet(mAccount.name, mAccount.type, mAccount.dataSet),
+ name, null, getActivity().getClass(), callbackAction);
+ } else {
+ serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
+ name, getActivity().getClass(), callbackAction);
+ }
+ ContactSaveService.startService(getActivity(), serviceIntent);
+ getListener().onGroupNameEditStarted(name);
+ dismiss();
+ }
+
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
@@ -163,10 +267,61 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_IS_INSERT, mIsInsert);
outState.putString(KEY_GROUP_NAME, getGroupName());
}
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // Only a single loader so id is ignored.
+ return new CursorLoader(getActivity(), GroupNameQuery.URI,
+ GroupNameQuery.PROJECTION, GroupNameQuery.getSelection(mAccount),
+ GroupNameQuery.getSelectionArgs(mAccount), null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ mExistingGroups = new HashSet<>();
+ while (data.moveToNext()) {
+ mExistingGroups.add(data.getString(GroupNameQuery.TITLE));
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ /**
+ * Defines the structure of the query performed by the CursorLoader created by
+ * GroupNameEditDialogFragment
+ */
+ private static class GroupNameQuery {
+
+ public static final int TITLE = 0;
+ public static final Uri URI = ContactsContract.Groups.CONTENT_URI;
+ public static final String[] PROJECTION = new String[] { ContactsContract.Groups.TITLE };
+
+ public static String getSelection(AccountWithDataSet account) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(ContactsContract.Groups.ACCOUNT_NAME).append("=? AND ")
+ .append(ContactsContract.Groups.ACCOUNT_TYPE).append("=?");
+ if (account.dataSet != null) {
+ builder.append(" AND ").append(ContactsContract.Groups.DATA_SET).append("=?");
+ }
+ return builder.toString();
+ }
+
+ public static String[] getSelectionArgs(AccountWithDataSet account) {
+ final int len = account.dataSet == null ? 2 : 3;
+ final String[] args = new String[len];
+ args[0] = account.name;
+ args[1] = account.type;
+ if (account.dataSet != null) {
+ args[2] = account.dataSet;
+ }
+ return args;
+ }
+ }
+
private void showInputMethod(View view) {
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
@@ -184,11 +339,13 @@
}
private Listener getListener() {
- if (!(getActivity() instanceof Listener)) {
- throw new ClassCastException(getActivity() + " must implement " +
- Listener.class.getName());
+ if (mListener != null) {
+ return mListener;
+ } else if (getActivity() instanceof Listener) {
+ return (Listener) getActivity();
+ } else {
+ return Listener.None;
}
- return (Listener) getActivity();
}
private String getGroupName() {
diff --git a/src/com/android/contacts/group/GroupsFragment.java b/src/com/android/contacts/group/GroupsFragment.java
index be1b44a..508c184 100644
--- a/src/com/android/contacts/group/GroupsFragment.java
+++ b/src/com/android/contacts/group/GroupsFragment.java
@@ -18,7 +18,6 @@
import android.app.Fragment;
import android.app.LoaderManager;
-import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
deleted file mode 100644
index e998688..0000000
--- a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 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.interactions;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.EditText;
-
-import com.android.contacts.ContactSaveService;
-import com.android.contacts.R;
-import com.android.contacts.activities.ContactEditorBaseActivity;
-import com.android.contacts.common.model.account.AccountWithDataSet;
-
-/**
- * A dialog for creating a new group.
- */
-public class GroupCreationDialogFragment extends GroupNameDialogFragment {
- private static final String ARG_ACCOUNT_TYPE = "accountType";
- private static final String ARG_ACCOUNT_NAME = "accountName";
- private static final String ARG_DATA_SET = "dataSet";
-
- public static final String FRAGMENT_TAG = "createGroupDialog";
-
- private final OnGroupCreatedListener mListener;
-
- public interface OnGroupCreatedListener {
- public void onGroupCreated();
- }
-
- public static void show(
- FragmentManager fragmentManager, String accountType, String accountName,
- String dataSet, OnGroupCreatedListener listener) {
- GroupCreationDialogFragment dialog = new GroupCreationDialogFragment(listener);
- Bundle args = new Bundle();
- args.putString(ARG_ACCOUNT_TYPE, accountType);
- args.putString(ARG_ACCOUNT_NAME, accountName);
- args.putString(ARG_DATA_SET, dataSet);
- dialog.setArguments(args);
- dialog.show(fragmentManager, FRAGMENT_TAG);
- }
-
- public GroupCreationDialogFragment() {
- super();
- mListener = null;
- }
-
- private GroupCreationDialogFragment(OnGroupCreatedListener listener) {
- super();
- mListener = listener;
- }
-
- public OnGroupCreatedListener getOnGroupCreatedListener() {
- return mListener;
- }
-
- @Override
- protected void initializeGroupLabelEditText(EditText editText) {
- }
-
- @Override
- protected int getTitleResourceId() {
- return R.string.create_group_dialog_title;
- }
-
- @Override
- protected void onCompleted(String groupLabel) {
- Bundle arguments = getArguments();
- String accountType = arguments.getString(ARG_ACCOUNT_TYPE);
- String accountName = arguments.getString(ARG_ACCOUNT_NAME);
- String dataSet = arguments.getString(ARG_DATA_SET);
-
- // Indicate to the listener that a new group will be created.
- // If the device is rotated, mListener will become null, so that the
- // popup from GroupMembershipView will not be shown.
- if (mListener != null) {
- mListener.onGroupCreated();
- }
-
- Activity activity = getActivity();
- activity.startService(ContactSaveService.createNewGroupIntent(activity,
- new AccountWithDataSet(accountName, accountType, dataSet), groupLabel,
- null /* no new members to add */,
- activity.getClass(), ContactEditorBaseActivity.ACTION_EDIT));
- }
-}
diff --git a/src/com/android/contacts/interactions/GroupNameDialogFragment.java b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
deleted file mode 100644
index 5efccfc..0000000
--- a/src/com/android/contacts/interactions/GroupNameDialogFragment.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2010 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.interactions;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnShowListener;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.InputFilter;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.android.contacts.R;
-
-/**
- * A common superclass for creating and renaming groups.
- */
-// TODO: consolidate it with GroupNameEditDialogFragment
-public abstract class GroupNameDialogFragment extends DialogFragment {
- protected abstract int getTitleResourceId();
- protected abstract void initializeGroupLabelEditText(EditText editText);
- protected abstract void onCompleted(String groupLabel);
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- final LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());
- final View view = layoutInflater.inflate(R.layout.group_name_dialog, null);
- final EditText editText = (EditText) view.findViewById(R.id.group_label);
- final int maxLength = getResources().getInteger(R.integer.group_name_max_length);
- editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxLength) });
- editText.setInputType(InputType.TYPE_CLASS_TEXT);
- initializeGroupLabelEditText(editText);
-
- builder.setTitle(getTitleResourceId());
- builder.setView(view);
- editText.requestFocus();
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int whichButton) {
- onCompleted(editText.getText().toString().trim());
- }
- }
- );
-
- builder.setNegativeButton(android.R.string.cancel, null);
- final AlertDialog dialog = builder.create();
-
- dialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialogInterface) {
- updateOkButtonState(dialog, editText);
- }
- });
- editText.addTextChangedListener(new TextWatcher() {
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- updateOkButtonState(dialog, editText);
- }
- });
- dialog.getWindow().setSoftInputMode(
- WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- return dialog;
- }
-
- /* package */ void updateOkButtonState(AlertDialog dialog, EditText editText) {
- final Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
- okButton.setEnabled(!TextUtils.isEmpty(editText.getText().toString().trim()));
- }
-}
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));
+ }
+}